{
    Copyright (C) 2013 by Max Nazhalov

    This file contains generalized floating point<->ASCII conversion routines.
    It is included by the FLT_CONV.INC after setting-up correct conditional
    definitions, therefore it sholud not be used directly.
    Refer to FLT_CONV.INC for further explanation.

    This library is free software; you can redistribute it and/or modify it
    under the terms of the GNU Library General Public License as published by
    the Free Software Foundation; either version 2 of the License, or (at your
    option) any later version with the following modification:

    As a special exception, the copyright holders of this library give you
    permission to link this library with independent modules to produce an
    executable, regardless of the license terms of these independent modules,
    and to copy and distribute the resulting executable under terms of your
    choice, provided that you also meet, for each linked independent module,
    the terms and conditions of the license of that module. An independent
    module is a module which is not derived from or based on this library.
    If you modify this library, you may extend this exception to your version
    of the library, but you are not obligated to do so. If you do not wish to
    do so, delete this exception statement from your version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
    See the GNU Library General Public License for more details.

    You should have received a copy of the GNU Library General Public License
    along with this library; if not, write to the Free Software Foundation,
    Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

 ****************************************************************************
}

type
    // "Do-It-Yourself Floating Point" structure
    TDIY_FP = record
      {$ifdef VALREAL_128}
        fh : qword;
      {$endif}
      {$ifdef VALREAL_32}
        f : dword;
      {$else}
        f : qword;
      {$endif}
      {$ifdef VALREAL_80}
        fh : dword;
      {$endif}
        e : integer;
    end;

    TDIY_FP_Power_of_10 = record
        c : TDIY_FP;
        e10 : integer;
    end;

{$if defined(VALREAL_80) or defined(VALREAL_128)}

(****************************************************************************
 * diy_util_add
 *
 * Helper routine: summ of multiword unsigned integers
 *
 * Used by:
 *   [80,128] diy_fp_cached_power10
 *   [80,128] diy_fp_multiply
 *   [80,128] float<->ASCII
 *
 ****************************************************************************)
{$ifdef VALREAL_80}
procedure diy_util_add( var xh: dword; var xl: qword; const yh: dword; const yl: qword ); {$ifdef grisu1_inline}inline;{$endif}
{$else VALREAL_128}
procedure diy_util_add( var xh, xl: qword; const yh, yl: qword ); {$ifdef grisu1_inline}inline;{$endif}
{$endif VALREAL_*}
var
    temp: qword;
begin
    temp := xl + yl;
    xh := xh + yh + ord( temp < xl );
    xl := temp;
end;

(****************************************************************************
 * diy_util_shl
 *
 * Helper routine: left shift of multiword unsigned integer
 *
 * Used by:
 *   [80,128] float<->ASCII
 *
 ****************************************************************************)
{$ifdef VALREAL_80}
procedure diy_util_shl( var h: dword; var l: qword; const count: integer );
{$else VALREAL_128}
procedure diy_util_shl( var h, l: qword; const count: integer );
{$endif VALREAL_*}
begin
    if ( count = 0 ) then
        exit;
{$ifdef grisu1_debug}
    assert( count > 0 );
  {$ifdef VALREAL_80}
    assert( count < 96 );
  {$else VALREAL_128}
    assert( count < 128 );
  {$endif VALREAL_*}
{$endif grisu1_debug}
    if ( count = 1 ) then
    begin
        diy_util_add( h, l, h, l );
        exit;
    end;
    if ( count >= 64 ) then
    begin
        if ( count > 64 ) then
            h := ( l shl ( count - 64 ) )
        else
            h := l;
        l := 0;
        exit;
    end;
  {$ifdef VALREAL_80}
    if ( count = 32 ) then
        h := hi( l )
    else
  {$endif VALREAL_80}
    if ( count < 32 ) then
        h := ( h shl count ) + ( hi( l ) shr ( 32 - count ) )
    else
  {$ifdef VALREAL_80}
        h := ( l shr ( 64 - count ) );
  {$else VALREAL_128}
        h := ( h shl count ) + ( l shr ( 64 - count ) );
  {$endif VALREAL_*}
    l := ( l shl count );
end;

(****************************************************************************
 * diy_util_shr
 *
 * Helper routine: right shift of multiword unsigned integer
 *
 * Used by:
 *   [80,128] float<->ASCII
 *
 ****************************************************************************)
{$ifdef VALREAL_80}
procedure diy_util_shr( var h: dword; var l: qword; const count: integer );
{$else VALREAL_128}
procedure diy_util_shr( var h, l: qword; const count: integer );
{$endif VALREAL_*}
begin
    if ( count = 0 ) then
        exit;
  {$ifdef grisu1_debug}
    assert( count > 0 );
  {$endif grisu1_debug}
    if ( count = 1 ) then
    begin
        l := l shr 1;
        if ( lo(h) and 1 <> 0 ) then
            l := l or qword($8000000000000000);
        h := h shr 1;
        exit;
    end;
    if ( count < 64 ) then
    begin
        l := ( qword( h ) shl ( ( - count ) and 63 ) ) or ( l shr count );
      {$ifdef VALREAL_80}
        if ( count >= 32 ) then
            h := 0
        else
      {$endif VALREAL_80}
            h := h shr count;
        exit;
    end;
  {$ifdef VALREAL_80}
    if ( count < 96 ) then
  {$else VALREAL_128}
    if ( count < 128 ) then
  {$endif VALREAL_*}
        l := h shr ( count and 63 )
    else
        l := 0;
    h := 0;
end;

{$endif VALREAL_80 | VALREAL_128}

(****************************************************************************
 * diy_fp_multiply
 *
 * "Do-It-Yourself Floating Point" multiplication routine
 *
 * Simplified implementation:
 *  > restricted input:
 *     - both operands should be normalized
 *  > relaxed output:
 *     - rounding is simple [half is rounded-up]
 *     - normalization is optional and performed at the very end if requested
 *       [at most 1 shift required since both multipliers are normalized]
 *
 * Used by:
 *   [all] float<->ASCII
 *   [>32] diy_fp_cached_power10
 *
 ****************************************************************************)
function diy_fp_multiply( const x, y: TDIY_FP; normalize: boolean ): TDIY_FP;
const
    C_1_SHL_31 = dword($80000000);
{$ifdef VALREAL_32}
//***************** 32-bit *********************
var
    a, b, c, d, ac, bc, ad, bd, t1: dword;
begin
    a := ( x.f shr 16 );
    b := ( x.f and $0000FFFF );
    c := ( y.f shr 16 );
    d := ( y.f and $0000FFFF );
    ac := a * c;
    bc := b * c;
    ad := a * d;
    bd := b * d;
    t1 := ( bc and $0000FFFF )
        + ( bd shr 16 )
        + ( ad and $0000FFFF )
        + ( 1 shl 15 ); // round
    diy_fp_multiply.f := ac
        + ( ad shr 16 )
        + ( bc shr 16 )
        + ( t1 shr 16 );
    diy_fp_multiply.e := x.e + y.e + 32;
    if normalize then with diy_fp_multiply do
    begin
        if ( f and C_1_SHL_31 = 0 ) then
        begin
            inc( f, f );
            dec( e );
        end;
      {$ifdef grisu1_debug}
        assert( f and C_1_SHL_31 <> 0 );
      {$endif grisu1_debug}
    end;
end;
{$else not VALREAL_32}
(*-------------------------------------------------------
 | u32_mul_u32_to_u64 [local]
 |
 | Local routine of the "diy_fp_multiply"; common to float64..float128:
 |     uint32 * uint32 -> uint64
 |
 *-------------------------------------------------------*)
function u32_mul_u32_to_u64( const a, b: dword ): qword; {$ifdef grisu1_inline}inline;{$endif}
begin
    // it seems this pattern is very tightly optimized by FPC at least for i386
    u32_mul_u32_to_u64 := qword( a ) * b;
end;
{$endif VALREAL_*}
{$ifdef VALREAL_64}
//***************** 64-bit *********************
var
    a, b, c, d: dword;
    ac, bc, ad, bd, t1: qword;
begin
    a := hi( x.f ); b := x.f;
    c := hi( y.f ); d := y.f;
    ac := u32_mul_u32_to_u64( a, c );
    bc := u32_mul_u32_to_u64( b, c );
    ad := u32_mul_u32_to_u64( a, d );
    bd := u32_mul_u32_to_u64( b, d );
    t1 := qword( C_1_SHL_31 ) // round
        + hi( bd ) + lo( bc ) + lo( ad );
    diy_fp_multiply.f := ac + hi( ad ) + hi( bc ) + hi( t1 );
    diy_fp_multiply.e := x.e + y.e + 64;
    if normalize then with diy_fp_multiply do
    begin
        if ( hi( f ) and C_1_SHL_31 = 0 ) then
        begin
            inc( f, f );
            dec( e );
        end;
      {$ifdef grisu1_debug}
        assert( hi( f ) and C_1_SHL_31 <> 0 );
      {$endif grisu1_debug}
    end;
end;
{$endif VALREAL_64}
{$ifdef VALREAL_80}
//***************** 96-bit *********************
var
    a, b, c, u, v, w: dword;
    au, av, aw, bu, bv, bw, cu, cv, cw, t1, t2: qword;
begin
    a := x.fh; b := hi( x.f ); c := x.f;
    u := y.fh; v := hi( y.f ); w := y.f;
    au := u32_mul_u32_to_u64( a, u );
    bu := u32_mul_u32_to_u64( b, u );
    cu := u32_mul_u32_to_u64( c, u );
    av := u32_mul_u32_to_u64( a, v );
    bv := u32_mul_u32_to_u64( b, v );
    cv := u32_mul_u32_to_u64( c, v );
    aw := u32_mul_u32_to_u64( a, w );
    bw := u32_mul_u32_to_u64( b, w );
    cw := u32_mul_u32_to_u64( c, w );
    t1 := ( cw shr 32 ) + lo( bw ) + lo( cv );
    t1 := qword( C_1_SHL_31 ) // round
        + hi( t1 ) + hi( bw ) + hi( cv ) + lo( aw ) + lo( bv ) + lo( cu );
    t1 := ( t1 shr 32 ) + hi( aw ) + hi( bv ) + hi( cu ) + lo( av ) + lo( bu );
    t2 := au + hi( av ) + hi( bu ) + hi( t1 );
    diy_fp_multiply.f := ( t2 shl 32 ) + lo( t1 );
    diy_fp_multiply.fh := hi( t2 );
    diy_fp_multiply.e := x.e + y.e + 96;
    if normalize then with diy_fp_multiply do
    begin
        if ( fh and C_1_SHL_31 = 0 ) then
        begin
            diy_util_add( fh, f, fh, f );
            dec( e );
        end;
      {$ifdef grisu1_debug}
        assert( fh and C_1_SHL_31 <> 0 );
      {$endif grisu1_debug}
    end;
end;
{$endif VALREAL_80}
{$ifdef VALREAL_128}
//***************** 128-bit ********************
var
    a, b, c, d, u, v, w, z: dword;
    au, av, aw, az, bu, bv, bw, bz, cu, cv, cw, cz, du, dv, dw, dz, t1, t2: qword;
begin
    a := hi( x.fh ); b := x.fh; c := hi( x.f ); d := x.f;
    u := hi( y.fh ); v := y.fh; w := hi( y.f ); z := y.f;
    au := u32_mul_u32_to_u64( a, u );
    bu := u32_mul_u32_to_u64( b, u );
    cu := u32_mul_u32_to_u64( c, u );
    du := u32_mul_u32_to_u64( d, u );
    av := u32_mul_u32_to_u64( a, v );
    bv := u32_mul_u32_to_u64( b, v );
    cv := u32_mul_u32_to_u64( c, v );
    dv := u32_mul_u32_to_u64( d, v );
    aw := u32_mul_u32_to_u64( a, w );
    bw := u32_mul_u32_to_u64( b, w );
    cw := u32_mul_u32_to_u64( c, w );
    dw := u32_mul_u32_to_u64( d, w );
    az := u32_mul_u32_to_u64( a, z );
    bz := u32_mul_u32_to_u64( b, z );
    cz := u32_mul_u32_to_u64( c, z );
    dz := u32_mul_u32_to_u64( d, z );
    t1 := ( dz shr 32 ) + lo( cz ) + lo( dw );
    t1 := ( t1 shr 32 ) + hi( cz ) + hi( dw ) + lo( bz ) + lo( cw ) + lo( dv );
    t1 := qword( C_1_SHL_31 ) // round
        + hi( t1 ) + hi( bz ) + hi( cw ) + hi( dv ) + lo( az ) + lo( bw ) + lo( cv ) + lo( du );
    t2 := ( t1 shr 32 ) + hi( az ) + hi( bw ) + hi( cv ) + hi( du ) + lo( aw ) + lo( bv ) + lo( cu );
    t1 := ( t2 shr 32 ) + hi( aw ) + hi( bv ) + hi( cu ) + lo( av ) + lo( bu );
    diy_fp_multiply.f := ( t1 shl 32 ) + lo( t2 );
    diy_fp_multiply.fh := au + hi( av ) + hi( bu ) + hi( t1 );
    diy_fp_multiply.e := x.e + y.e + 128;
    if normalize then with diy_fp_multiply do
    begin
        if ( hi( fh ) and C_1_SHL_31 = 0 ) then
        begin
            diy_util_add( fh, f, fh, f );
            dec( e );
        end;
      {$ifdef grisu1_debug}
        assert( hi( fh ) and C_1_SHL_31 <> 0 );
      {$endif grisu1_debug}
    end;
end;
{$endif VALREAL_128}

(****************************************************************************
 * diy_fp_cached_power10
 *
 * The main purpose of this routine is to return normalized correctly rounded
 * DIY-floating-point approximation of the power of 10, which has to be used
 * by the Grisu1 as a scaling factor, intended to shift a binary exponent of
 * the original number into selected [ alpha .. gamma ] range.
 *
 * This routine is also usable as a helper during ASCII -> float conversion,
 * so the range of cached powers is slightly extended beyond the requirements
 * of the Grisu1.
 *
 * Used by:
 *   [all] float<->ASCII
 *
 ****************************************************************************)
procedure diy_fp_cached_power10( exp10: integer; out factor: TDIY_FP_Power_of_10 );
{$ifdef VALREAL_32}
const
    // alpha =-29; gamma = 0; step = 1E+8
    cache: array [ 0 .. 13 ] of TDIY_FP_Power_of_10 = (
        ( c: ( f: dword($FB158593); e: -218 ); e10: -56 ),
        ( c: ( f: dword($BB127C54); e: -191 ); e10: -48 ),
        ( c: ( f: dword($8B61313C); e: -164 ); e10: -40 ),
        ( c: ( f: dword($CFB11EAD); e: -138 ); e10: -32 ),
        ( c: ( f: dword($9ABE14CD); e: -111 ); e10: -24 ),
        ( c: ( f: dword($E69594BF); e:  -85 ); e10: -16 ),
        ( c: ( f: dword($ABCC7712); e:  -58 ); e10:  -8 ),
        ( c: ( f: dword($80000000); e:  -31 ); e10:   0 ),
        ( c: ( f: dword($BEBC2000); e:   -5 ); e10:   8 ),
        ( c: ( f: dword($8E1BC9BF); e:   22 ); e10:  16 ),
        ( c: ( f: dword($D3C21BCF); e:   48 ); e10:  24 ),
        ( c: ( f: dword($9DC5ADA8); e:   75 ); e10:  32 ),
        ( c: ( f: dword($EB194F8E); e:  101 ); e10:  40 ),
        ( c: ( f: dword($AF298D05); e:  128 ); e10:  48 )
    );
var
    i, min10: integer;
begin
    // find index
    min10 := cache[ low( cache ) ].e10;
    if ( exp10 <= min10 ) then
        i := 0
    else
    begin
        i := ( exp10 - min10 ) div 8;
        if ( i >= high(cache) ) then
            i := high(cache)
        else
            if ( cache[ i ].e10 <> exp10 ) then
                inc( i ); // round-up
    end;
    // generate result
    factor := cache[ i ];
end;
{$endif VALREAL_32}
//**************************************
{$ifdef VALREAL_64}
const
    // alpha =-61; gamma = 0
    // full cache: 1E-450 .. 1E+432, step = 1E+18
    // sparse = 1/10
    C_PWR10_DELTA = 18;
    C_PWR10_COUNT = 50;
    base: array [ 0 .. 9 ] of TDIY_FP_Power_of_10 = (
        ( c: ( f: qword($825ECC24C8737830); e: -362 ); e10:  -90 ),
        ( c: ( f: qword($E2280B6C20DD5232); e: -303 ); e10:  -72 ),
        ( c: ( f: qword($C428D05AA4751E4D); e: -243 ); e10:  -54 ),
        ( c: ( f: qword($AA242499697392D3); e: -183 ); e10:  -36 ),
        ( c: ( f: qword($9392EE8E921D5D07); e: -123 ); e10:  -18 ),
        ( c: ( f: qword($8000000000000000); e:  -63 ); e10:    0 ),
        ( c: ( f: qword($DE0B6B3A76400000); e:   -4 ); e10:   18 ),
        ( c: ( f: qword($C097CE7BC90715B3); e:   56 ); e10:   36 ),
        ( c: ( f: qword($A70C3C40A64E6C52); e:  116 ); e10:   54 ),
        ( c: ( f: qword($90E40FBEEA1D3A4B); e:  176 ); e10:   72 )
    );
    factor_plus: array [ 0 .. 1 ] of TDIY_FP_Power_of_10 = (
        ( c: ( f: qword($F6C69A72A3989F5C); e:   534 ); e10:  180 ),
        ( c: ( f: qword($EDE24AE798EC8284); e:  1132 ); e10:  360 ) 
    );
    factor_minus: array [ 0 .. 1 ] of TDIY_FP_Power_of_10 = (
        ( c: ( f: qword($84C8D4DFD2C63F3B); e:  -661 ); e10: -180 ),
        ( c: ( f: qword($89BF722840327F82); e: -1259 ); e10: -360 ) 
    );
    corrector: array [ 0 .. C_PWR10_COUNT - 1 ] of shortint = (
    // extra mantissa correction [ulp; signed]
        0,  0,  0,  0,  1,  0,  0,  0,  1, -1,
        0,  1,  1,  1, -1,  0,  0,  1,  0, -1,
        0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
       -1,  0,  0, -1,  0,  0,  0,  0,  0, -1,
        0,  0,  0,  0,  1,  0,  0,  0, -1,  0
    );
{$endif VALREAL_64}
//**************************************
{$ifdef VALREAL_80}
const
    // alpha =-93; gamma =+30
    // full cache: 1E-5032 .. 1E+4995, step = 1E+37
    // sparse = 1/16
    C_PWR10_DELTA = 37;
    C_PWR10_COUNT = 272;
    base: array [ 0 .. 15 ] of TDIY_FP_Power_of_10 = (
        ( c: ( f: qword($07286FAA1AF5AF66); fh: dword($D1476E2C); e:  -1079 ); e10:  -296 ),
        ( c: ( f: qword($99107C22CB550FB4); fh: dword($C4CE17B3); e:   -956 ); e10:  -259 ),
        ( c: ( f: qword($99F6858428E2557B); fh: dword($B9131798); e:   -833 ); e10:  -222 ),
        ( c: ( f: qword($4738705E9624AB51); fh: dword($AE0B158B); e:   -710 ); e10:  -185 ),
        ( c: ( f: qword($0D5FDAF5C13E60D1); fh: dword($A3AB6658); e:   -587 ); e10:  -148 ),
        ( c: ( f: qword($163FA42E504BCED2); fh: dword($99EA0196); e:   -464 ); e10:  -111 ),
        ( c: ( f: qword($483BB9B9B1C6F22B); fh: dword($90BD77F3); e:   -341 ); e10:   -74 ),
        ( c: ( f: qword($545C75757E50D641); fh: dword($881CEA14); e:   -218 ); e10:   -37 ),
        ( c: ( f: qword($0000000000000000); fh: dword($80000000); e:    -95 ); e10:     0 ),
        ( c: ( f: qword($BB48DB201E86D400); fh: dword($F0BDC21A); e:     27 ); e10:    37 ),
        ( c: ( f: qword($4DCDAB14C696963C); fh: dword($E264589A); e:    150 ); e10:    74 ),
        ( c: ( f: qword($C1D1EA966C9E18AC); fh: dword($D4E5E2CD); e:    273 ); e10:   111 ),
        ( c: ( f: qword($C8965D3D6F928295); fh: dword($C83553C5); e:    396 ); e10:   148 ),
        ( c: ( f: qword($96706114873D5D9F); fh: dword($BC4665B5); e:    519 ); e10:   185 ),
        ( c: ( f: qword($56105DAD7425A83F); fh: dword($B10D8E14); e:    642 ); e10:   222 ),
        ( c: ( f: qword($B84603568A892ABB); fh: dword($A67FF273); e:    765 ); e10:   259 )
    );
    factor_plus: array [ 0 .. 7 ] of TDIY_FP_Power_of_10 = (
        ( c: ( f: qword($3576D3D149738BA0); fh: dword($BF87DECC); e:   1871 ); e10:   592 ),
        ( c: ( f: qword($750E83050A40DE03); fh: dword($8F4C0691); e:   3838 ); e10:  1184 ),
        ( c: ( f: qword($727E5D9756BC4BF8); fh: dword($D66B8D68); e:   5804 ); e10:  1776 ),
        ( c: ( f: qword($CE9DB63FD51AF6A3); fh: dword($A06C0BD4); e:   7771 ); e10:  2368 ),
        ( c: ( f: qword($5A7ADBC5B8787D89); fh: dword($F00B82D7); e:   9737 ); e10:  2960 ),
        ( c: ( f: qword($22D732D7AE7EDAA7); fh: dword($B397FD9A); e:  11704 ); e10:  3552 ),
        ( c: ( f: qword($CCD2839E0367500B); fh: dword($865DB7A9); e:  13671 ); e10:  4144 ),
        ( c: ( f: qword($FCBEE713F3BE171A); fh: dword($C90E78C7); e:  15637 ); e10:  4736 )
    );
    factor_minus: array [ 0 .. 7 ] of TDIY_FP_Power_of_10 = (
        ( c: ( f: qword($2F85DC7AE66FEACF); fh: dword($AB15B5D2); e:  -2062 ); e10:  -592 ),
        ( c: ( f: qword($4237088F4C7284FA); fh: dword($E4AC057C); e:  -4029 ); e10: -1184 ),
        ( c: ( f: qword($D2DCB34CEC42875C); fh: dword($98D24C2F); e:  -5995 ); e10: -1776 ),
        ( c: ( f: qword($B50918191D8106CD); fh: dword($CC42DD5C); e:  -7962 ); e10: -2368 ),
        ( c: ( f: qword($10CF24303CA163B8); fh: dword($8881FC6C); e:  -9928 ); e10: -2960 ),
        ( c: ( f: qword($BF10EA474FE1E9B1); fh: dword($B674CE73); e: -11895 ); e10: -3552 ),
        ( c: ( f: qword($478E074A0E85FC7F); fh: dword($F3DEFE25); e: -13862 ); e10: -4144 ),
        ( c: ( f: qword($A3BD093CC62364C2); fh: dword($A2FAA242); e: -15828 ); e10: -4736 )
    );
    corrector: array [ 0 .. C_PWR10_COUNT - 1 ] of shortint = (
    // extra mantissa correction [ulp; signed]
        0,  0,  0,  1,  0,  0,  1,  1,  0,  0,  1,  1, -1,  1,  0,  0,
        0,  1,  0,  0,  0,  0,  0,  1,  0,  0,  0,  0,  0,  0,  0,  0,
        0,  0,  0,  0,  0,  0,  1,  2,  2,  0,  1,  1,  0,  0, -2,  0,
        2,  0,  1,  1,  1,  1,  1,  2,  0,  0,  2,  1,  0,  1,  0,  0,
        0,  0,  1, -1,  0,  0,  1,  1,  0,  0,  1,  0, -1,  0, -1,  0,
        0,  0,  1,  0,  0,  0,  1,  0,  0,  0,  1,  1, -1,  0, -1,  1,
        0,  0,  0,  0,  0,  0,  0,  1,  0,  0,  0,  0,  0,  0, -1,  0,
       -1,  0,  0, -1,  0, -1,  1,  1,  0, -1,  0,  0, -1, -1, -1,  0,
        0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
       -1,  0,  0,  0,  0, -1,  0, -1,  0,  0,  0,  0,  0,  0,  0,  0,
        2,  2,  1,  1,  0,  0,  0,  2,  0,  0,  1,  1,  0,  0,  1,  1,
        0,  0,  1,  0,  0,  0,  1,  2,  0,  0,  1,  0,  0,  0, -1,  0,
        0,  0,  2,  0,  0,  0,  1,  1,  0,  0,  0,  1, -1,  1,  0,  1,
        0,  0,  0, -1,  0,  0,  0,  1,  0,  0,  1,  0,  0,  0,  0,  0,
        0,  0,  1,  1, -1,  0,  0,  2,  0,  0,  1,  1,  0,  1,  1,  1,
       -1, -1,  1, -2,  0,  0,  0, -1,  1, -1,  1, -1, -1, -1,  0,  0,
        1,  1,  0,  0,  0,  0,  1,  1,  0,  0,  1,  0,  0,  0,  0,  0
    );
{$endif VALREAL_80}
//**************************************
{$ifdef VALREAL_128}
const
    // alpha =-125; gamma = -2
    // full cache: 1E-5032 .. 1E+4995, step = 1E+37
    // sparse = 1/16
    C_PWR10_DELTA = 37;
    C_PWR10_COUNT = 272;
    base: array [ 0 .. 15 ] of TDIY_FP_Power_of_10 = (
        ( c: ( fh: qword($D1476E2C07286FAA); f: qword($1AF5AF660DB4AEE2); e:  -1111 ); e10:  -296 ),
        ( c: ( fh: qword($C4CE17B399107C22); f: qword($CB550FB4384D21D4); e:   -988 ); e10:  -259 ),
        ( c: ( fh: qword($B913179899F68584); f: qword($28E2557B59846E3F); e:   -865 ); e10:  -222 ),
        ( c: ( fh: qword($AE0B158B4738705E); f: qword($9624AB50B148D446); e:   -742 ); e10:  -185 ),
        ( c: ( fh: qword($A3AB66580D5FDAF5); f: qword($C13E60D0D2E0EBBA); e:   -619 ); e10:  -148 ),
        ( c: ( fh: qword($99EA0196163FA42E); f: qword($504BCED1BF8E4E46); e:   -496 ); e10:  -111 ),
        ( c: ( fh: qword($90BD77F3483BB9B9); f: qword($B1C6F22B5E6F48C3); e:   -373 ); e10:   -74 ),
        ( c: ( fh: qword($881CEA14545C7575); f: qword($7E50D64177DA2E55); e:   -250 ); e10:   -37 ),
        ( c: ( fh: qword($8000000000000000); f: qword($0000000000000000); e:   -127 ); e10:     0 ),
        ( c: ( fh: qword($F0BDC21ABB48DB20); f: qword($1E86D40000000000); e:     -5 ); e10:    37 ),
        ( c: ( fh: qword($E264589A4DCDAB14); f: qword($C696963C7EED2DD2); e:    118 ); e10:    74 ),
        ( c: ( fh: qword($D4E5E2CDC1D1EA96); f: qword($6C9E18AC7007C91A); e:    241 ); e10:   111 ),
        ( c: ( fh: qword($C83553C5C8965D3D); f: qword($6F92829494E5ACC7); e:    364 ); e10:   148 ),
        ( c: ( fh: qword($BC4665B596706114); f: qword($873D5D9F0DDE1FEF); e:    487 ); e10:   185 ),
        ( c: ( fh: qword($B10D8E1456105DAD); f: qword($7425A83E872C5F47); e:    610 ); e10:   222 ),
        ( c: ( fh: qword($A67FF273B8460356); f: qword($8A892ABAF368F137); e:    733 ); e10:   259 )
    );
    factor_plus: array [ 0 .. 7 ] of TDIY_FP_Power_of_10 = (
        ( c: ( fh: qword($BF87DECC3576D3D1); f: qword($49738B9F99B4642D); e:   1839 ); e10:   592 ),
        ( c: ( fh: qword($8F4C0691750E8305); f: qword($0A40DE037C9AD730); e:   3806 ); e10:  1184 ),
        ( c: ( fh: qword($D66B8D68727E5D97); f: qword($56BC4BF837B34968); e:   5772 ); e10:  1776 ),
        ( c: ( fh: qword($A06C0BD4CE9DB63F); f: qword($D51AF6A3244A6983); e:   7739 ); e10:  2368 ),
        ( c: ( fh: qword($F00B82D75A7ADBC5); f: qword($B8787D891AB45D5B); e:   9705 ); e10:  2960 ),
        ( c: ( fh: qword($B397FD9A22D732D7); f: qword($AE7EDAA76FBBD923); e:  11672 ); e10:  3552 ),
        ( c: ( fh: qword($865DB7A9CCD2839E); f: qword($0367500A8E9A1790); e:  13639 ); e10:  4144 ),
        ( c: ( fh: qword($C90E78C7FCBEE713); f: qword($F3BE171A27BF81DB); e:  15605 ); e10:  4736 )
    );
    factor_minus: array [ 0 .. 7 ] of TDIY_FP_Power_of_10 = (
        ( c: ( fh: qword($AB15B5D22F85DC7A); f: qword($E66FEACEB7836F69); e:  -2094 ); e10:  -592 ),
        ( c: ( fh: qword($E4AC057C4237088F); f: qword($4C7284F9EDDA793D); e:  -4061 ); e10: -1184 ),
        ( c: ( fh: qword($98D24C2FD2DCB34C); f: qword($EC42875C0B22B986); e:  -6027 ); e10: -1776 ),
        ( c: ( fh: qword($CC42DD5CB5091819); f: qword($1D8106CCF8EE85B4); e:  -7994 ); e10: -2368 ),
        ( c: ( fh: qword($8881FC6C10CF2430); f: qword($3CA163B873AA88A6); e:  -9960 ); e10: -2960 ),
        ( c: ( fh: qword($B674CE73BF10EA47); f: qword($4FE1E9B0FCDF7B3D); e: -11927 ); e10: -3552 ),
        ( c: ( fh: qword($F3DEFE25478E074A); f: qword($0E85FC7F4EDBD3CB); e: -13894 ); e10: -4144 ),
        ( c: ( fh: qword($A2FAA242A3BD093C); f: qword($C62364C260A887E2); e: -15860 ); e10: -4736 )
    );
    corrector: array [ 0 .. C_PWR10_COUNT - 1 ] of shortint = (
      // extra mantissa correction [ulp; signed]
        1,  0,  1,  0,  1,  1,  0,  0,  0,  0,  0,  1,  0,  0,  2,  0,
       -1, -1,  0, -1,  0, -1,  0,  0, -1,  0,  0,  0,  0,  0,  0,  0,
        1,  0,  0,  0,  1, -1,  0, -1, -1,  1,  0,  1,  0,  0,  1,  1,
        0, -2,  0,  0,  0, -1,  0,  0,  0,  0, -2,  0,  0,  0,  0,  0,
        0, -1,  1,  0,  1,  0,  0, -1,  0,  1,  0,  0,  1,  0,  1,  1,
        1, -1,  0,  0,  1, -1,  0,  0,  0,  1,  0,  1,  1, -1,  1,  1,
        0,  0,  1,  0,  0,  0, -1,  0, -1,  0,  0,  0,  0,  0,  0,  1,
        0,  0,  2,  1,  0, -1, -1, -1, -1,  0, -1,  1,  0, -1,  0,  0,
        0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
        0,  0,  0, -1,  1, -1, -1,  0, -1,  0, -1,  0,  0,  0,  0,  0,
        1, -1,  2,  1,  2,  0, -1,  1,  0,  0,  0,  1,  2,  0,  1,  1,
        0,  0,  1,  0,  1,  0,  0,  0,  0,  1,  0,  1,  1,  0,  1,  1,
        0,  0,  0,  1,  0, -1, -1,  0, -1,  0,  0,  0,  0,  0,  0,  1,
       -1, -1,  0,  0,  0,  0,  0, -1, -1,  0,  0,  0,  1,  0,  0,  0,
        0,  0,  1,  0, -1,  0,  0,  0, -1,  0, -1,  0,  1,  0,  0, -1,
        0, -1,  1, -1,  1, -1,  0, -1,  0,  1, -1,  0,  1,  1,  1,  1,
        0, -1,  1, -1,  0, -2,  0, -1, -1,  0, -1,  0,  0, -1,  0,  0
    );
{$endif VALREAL_128}
//**************************************
{$ifndef VALREAL_32} // common for float64..float128
var
    i, xmul, inod, min10: integer;
    A: TDIY_FP_Power_of_10;
  {$ifdef VALREAL_80}
    ch: dword;
  {$endif}
  {$ifdef VALREAL_128}
    ch: qword;
  {$endif}
    cx: shortint;
begin
    // find non-sparse index
    min10 := base [ low( base ) ].e10 + factor_minus[ high( factor_minus ) ].e10;
    if ( exp10 <= min10 ) then
        i := 0
    else
    begin
        i := ( exp10 - min10 ) div C_PWR10_DELTA;
        if ( i * C_PWR10_DELTA + min10 <> exp10 ) then
            inc( i ); // round-up
        if ( i > C_PWR10_COUNT - 1 ) then
            i := C_PWR10_COUNT - 1;
    end;
    // generate result
    inod := i mod length( base );
    xmul := ( i div length( base ) ) - length( factor_minus );
    if ( xmul = 0 ) then
    begin
        // base
        factor := base[ inod ];
        exit;
    end;
    // surrogate
    A := base[ inod ];
    if ( xmul > 0 ) then
    begin
        dec( xmul );
        factor.e10 := A.e10 + factor_plus[ xmul ].e10;
        if ( A.e10 <> 0 ) then
            factor.c := diy_fp_multiply( A.c, factor_plus[ xmul ].c, TRUE )
        else
        begin
            // exact
            factor.c := factor_plus[ xmul ].c;
            exit;
        end;
    end
    else
    begin
        xmul := - ( xmul + 1 );
        factor.e10 := A.e10 + factor_minus[ xmul ].e10;
        if ( A.e10 <> 0 ) then
            factor.c := diy_fp_multiply( A.c, factor_minus[ xmul ].c, TRUE )
        else
        begin
            // exact
            factor.c := factor_minus[ xmul ].c;
            exit;
        end;
    end;
    // adjust mantissa
    cx := corrector[ i ];
    if ( cx <> 0 ) then
  {$ifdef VALREAL_64}
        inc( factor.c.f, int64( cx ) );
  {$else VALREAL_80 | VALREAL_128}
    begin
        ch := 0;
        if ( cx < 0 ) then
            dec( ch );
        diy_util_add( factor.c.fh, factor.c.f, ch, int64( cx ) );
    end;
  {$endif VALREAL_*}
    //
end;
{$endif VALREAL_64..VALREAL_128}

(*==========================================================================*
 *                                                                          *
 *                              Float -> ASCII                              *
 *                                                                          *
 *==========================================================================*)

procedure str_real( min_width, frac_digits: integer; const v: ValReal; real_type: TReal_Type; out str: shortstring );

{$undef VALREAL_PACK}
{$i flt_pack.inc}

const
{$ifdef VALREAL_32}
    C_FRAC2_BITS  = 23;
    C_EXP2_BIAS   = 127;
    C_DIY_FP_Q    = 32;
    C_GRISU_ALPHA =-29;
    C_GRISU_GAMMA = 0;
    RT_NATIVE = RT_S32REAL;
{$endif VALREAL_32}
{$ifdef VALREAL_64}
    C_FRAC2_BITS  = 52;
    C_EXP2_BIAS   = 1023;
    C_DIY_FP_Q    = 64;
    C_GRISU_ALPHA =-61;
    C_GRISU_GAMMA = 0;
    RT_NATIVE = RT_S64REAL;
{$endif VALREAL_64}
{$ifdef VALREAL_80}
    C_FRAC2_BITS  = 63;
    C_EXP2_BIAS   = 16383;
    C_DIY_FP_Q    = 96;
    C_GRISU_ALPHA =-93;
    C_GRISU_GAMMA = 30;
    RT_NATIVE = RT_S80REAL;
{$endif VALREAL_80}
{$ifdef VALREAL_128}
    C_FRAC2_BITS  = 112;
    C_EXP2_BIAS   = 16383;
    C_DIY_FP_Q    = 128;
    C_GRISU_ALPHA =-125;
    C_GRISU_GAMMA =-2;
    RT_NATIVE = RT_S128REAL;
{$endif VALREAL_128}

(****************************************************************************)
    // handy const
    C_EXP2_SPECIAL = C_EXP2_BIAS * 2 + 1;
{$ifdef VALREAL_32}
    C_MANT2_INTEGER = dword(1) shl C_FRAC2_BITS;
{$endif VALREAL_32}
{$if defined(VALREAL_64) or defined(VALREAL_80)}
    C_MANT2_INTEGER = qword(1) shl C_FRAC2_BITS;
{$endif VALREAL_64 | VALREAL_80}
{$ifdef VALREAL_128}
    C_MANT2_INTEGER_H = qword(1) shl ( C_FRAC2_BITS - 64 );
{$endif VALREAL_128}

    C_MAX_WIDTH = 255; // shortstring

{$ifdef fpc_softfpu_implementation}
    C_NO_MIN_WIDTH = -1; // just for convenience
{$else}
    C_NO_MIN_WIDTH = -32767; // this value is the one used internally by FPC
{$endif}

type
    TAsciiDigits = array [ 0 .. 39 ] of byte;

(*-------------------------------------------------------
 | gen_digits_32 [local]
 | gen_digits_64 [local] (used only for float64..float128 -> ASCII)
 |
 | These routines perform conversion of 32-bit/64-bit unsigned integer
 | to the sequence of byte-sized decimal digits.
 |
 *-------------------------------------------------------*)
function gen_digits_32( out buf: TAsciiDigits; pos: integer; x: dword; pad_9zero: boolean = false ): integer;
const
    digits: array [ 0 .. 9 ] of dword = (
              0,
             10,
            100,
           1000,
          10000,
         100000,
        1000000,
       10000000,
      100000000,
     1000000000
    );
var
    n: integer;
    m, z: dword;
begin
    // Calculate amount of digits
    if ( x = 0 ) then
        // emit nothing if padding is not required
        n := 0
    else
    begin
        n :=( ( BSRdword( x ) + 1 ) * 1233 ) shr 12;
        if ( x >= digits[ n ] ) then
            inc( n );
    end;
    if pad_9zero and ( n < 9 ) then
        n := 9;
    gen_digits_32 := n;
    // Emit digits
    m := x;
    while ( n > 0 ) do
    begin
        dec( n );
        if ( m <> 0 ) then
        begin
            z := m div 10;
            buf[ pos + n ] := m - z * 10;
            m := z;
        end
        else
            buf[ pos + n ] := 0;
    end;
end;

{$ifndef VALREAL_32}
function gen_digits_64( out buf: TAsciiDigits; pos: integer; const x: qword; pad_19zero: boolean = false ): integer;
var
    n_digits: integer;
    temp: qword;
    splitl, splitm, splith: dword;
begin
    // Split X into 3 unsigned 32-bit integers; lower two should be less than 10 digits long
    if ( x < 1000000000 ) then
    begin
        splith := 0;
        splitm := 0;
        splitl := x;
    end
    else
    begin
        temp := x div 1000000000;
        splitl := x - temp * 1000000000;
        if ( temp < 1000000000 ) then
        begin
            splith := 0;
            splitm := temp;
        end
        else
        begin
            splith := temp div 1000000000;
            splitm := lo( temp ) - splith * 1000000000;
        end;
    end;
    // Generate digits
    n_digits := gen_digits_32( buf, pos, splith, false );
    if pad_19zero and ( n_digits = 0 ) then
    begin
        // at most 18 digits expected from splitm and splitl, so add one more
        buf[ pos ] := 0;
        n_digits := 1;
    end;
    inc( n_digits, gen_digits_32( buf, pos + n_digits, splitm, n_digits <> 0 ) );
    inc( n_digits, gen_digits_32( buf, pos + n_digits, splitl, n_digits <> 0 ) );
    gen_digits_64 := n_digits;
end;
{$endif VALREAL_64..VALREAL_128}

(*-------------------------------------------------------
 | round_digits [local]
 |
 | Performs digit sequence rounding, returns decimal point correction.
 |
 *-------------------------------------------------------*)
function round_digits( var buf: TAsciiDigits; var n_current: integer; n_max: integer; half_round_to_even: boolean = true ): integer;
var
    n: integer;
    dig_round, dig_sticky: byte;
  {$ifdef GRISU1_F2A_AGRESSIVE_ROUNDUP}
    i: integer;
  {$endif}
begin
    round_digits := 0;
    n := n_current;
{$ifdef grisu1_debug}
    assert( n_max >= 0 );
    assert( n_max < n );
{$endif grisu1_debug}
    n_current := n_max;
    // Get round digit
    dig_round := buf[n_max];

{$ifdef GRISU1_F2A_AGRESSIVE_ROUNDUP}
    // Detect if rounding-up the second last digit turns the "dig_round"
    // into "5"; also make sure we have at least 1 digit between "dig_round"
    // and the second last.
    if not half_round_to_even then
        if ( dig_round = 4 ) and ( n_max < n - 3 ) then
            if ( buf[ n - 2 ] >= 8 ) then // somewhat arbitrary..
            begin
                // check for only "9" are in between
                i := n - 2;
                repeat
                    dec( i );
                until ( i = n_max ) or ( buf[i] <> 9 );
                if ( i = n_max ) then
                    // force round-up
                    dig_round := 9; // any value ">=5"
            end;
{$endif}

    if ( dig_round < 5 ) then
        exit;

    // Handle "round half to even" case
    if ( dig_round = 5 ) and half_round_to_even and ( ( n_max = 0 ) or ( buf[ n_max - 1 ] and 1 = 0 ) ) then
    begin
        // even and a half: check if exactly the half
        dig_sticky := 0;
        while ( n > n_max + 1 ) and ( dig_sticky = 0 ) do
        begin
            dec( n );
            dig_sticky := buf[n];
        end;
        if ( dig_sticky = 0 ) then
            exit; // exactly a half -> no rounding is required
    end;

    // Round-up
    while ( n_max > 0 ) do
    begin
        dec( n_max );
        inc( buf[n_max] );
        if ( buf[n_max] < 10 ) then
        begin
            // no more overflow: stop now
            n_current := n_max + 1;
            exit;
        end;
        // continue rounding
    end;

    // Overflow out of the 1st digit, all n_max digits became 0
    buf[0] := 1;
    n_current := 1;
    round_digits := 1;
end;

(*-------------------------------------------------------
 | do_fillchar [local]
 |
 | Fills string region with certain character.
 |
 *-------------------------------------------------------*)
{$ifdef cpujvm}
procedure do_fillchar( var str: shortstring; pos, count: integer; c: char );
begin
  while count>0 do
    begin
      str[pos]:=c;
      inc(pos);
      dec(count);
    end;
end;
{$else not cpujvm}
procedure do_fillchar( var str: shortstring; pos, count: integer; c: char ); {$ifdef grisu1_inline}inline;{$endif}
begin
  fillchar( str[pos], count, c );
end;
{$endif cpujvm}

(*-------------------------------------------------------
 | try_return_fixed [local]
 |
 | This routine tries to format the number in the fixed-point representation.
 | If the resulting string is estimated to be too long to fit into shortstring,
 | routine returns FALSE giving the caller a chance to return the exponential
 | representation.
 | Otherwise, it returns TRUE.
 |
 | Not implemented [and why to do it at all?]:
 |     Here also a good place to limit the fixed point formatting by exponent
 |     range, falling back to exponential notation (just return FALSE).
 |
 *-------------------------------------------------------*)
function try_return_fixed( out str: shortstring; minus: boolean; const digits: TAsciiDigits; n_digits_have, fixed_dot_pos, min_width, frac_digits: integer ): boolean;
var
    rounded: boolean;
    temp_round: TAsciiDigits;
    i, j, len, cut_digits_at: integer;
    n_spaces, n_spaces_max, n_before_dot, n_before_dot_pad0, n_after_dot_pad0, n_after_dot, n_tail_pad0: integer;
begin
    try_return_fixed := false;
{$ifdef grisu1_debug}
    assert( n_digits_have >= 0 );
    assert( min_width <= C_MAX_WIDTH );
    assert( frac_digits >= 0 );
    assert( frac_digits <= C_MAX_WIDTH - 3 );
{$endif grisu1_debug}
    // Round digits if necessary
    rounded := false;
    cut_digits_at := fixed_dot_pos + frac_digits;
    if ( cut_digits_at < 0 ) then
        // zero
        n_digits_have := 0
    else
    if ( cut_digits_at < n_digits_have ) then
    begin
        // round digits
{$ifdef cpujvm}
        temp_round := digits;
{$else not cpujvm}
        if ( n_digits_have > 0 ) then
            move( digits, temp_round, n_digits_have * sizeof( digits[0] ) );
{$endif cpujvm}
        inc( fixed_dot_pos, round_digits( temp_round, n_digits_have, cut_digits_at {$ifdef GRISU1_F2A_HALF_ROUNDUP}, false {$endif} ) );
        rounded := true;
    end;
    // Before dot: digits, pad0
    if ( fixed_dot_pos <= 0 ) or ( n_digits_have = 0 ) then
    begin
        n_before_dot := 0;
        n_before_dot_pad0 := 1;
    end
    else
    if ( fixed_dot_pos > n_digits_have ) then
    begin
        n_before_dot := n_digits_have;
        n_before_dot_pad0 := fixed_dot_pos - n_digits_have;
    end
    else
    begin
        n_before_dot := fixed_dot_pos;
        n_before_dot_pad0 := 0;
    end;
    // After dot: pad0, digits, pad0
    if ( fixed_dot_pos < 0 ) then
        n_after_dot_pad0 := - fixed_dot_pos
    else
        n_after_dot_pad0 := 0;
    if ( n_after_dot_pad0 > frac_digits ) then
        n_after_dot_pad0 := frac_digits;
    n_after_dot := n_digits_have - n_before_dot;
    n_tail_pad0 := frac_digits - n_after_dot - n_after_dot_pad0;
{$ifdef grisu1_debug}
    assert( n_tail_pad0 >= 0 );
{$endif grisu1_debug}
    // Estimate string length
    len := ord( minus ) + n_before_dot + n_before_dot_pad0;
    if ( frac_digits > 0 ) then
        inc( len, n_after_dot_pad0 + n_after_dot + n_tail_pad0 + 1 );
    n_spaces_max := C_MAX_WIDTH - len;
    if ( n_spaces_max < 0 ) then
        exit;
    // Calculate space-padding length
    n_spaces := min_width - len;
    if ( n_spaces > n_spaces_max ) then
        n_spaces := n_spaces_max;
    if ( n_spaces > 0 ) then
        inc( len, n_spaces );
    // Allocate storage
    SetLength( str, len );
    i := 1;
    // Leading spaces
    if ( n_spaces > 0 ) then
    begin
        do_fillchar( str, i, n_spaces, ' ' );
        inc( i, n_spaces );
    end;
    // Sign
    if minus then
    begin
        str[i] := '-';
        inc( i );
    end;
    // Integer significant digits
    j := 0;
    if rounded then
        while ( n_before_dot > 0 ) do
        begin
            str[i] := char( temp_round[j] + ord('0') );
            inc( i );
            inc( j );
            dec( n_before_dot );
        end
    else
        while ( n_before_dot > 0 ) do
        begin
            str[i] := char( digits[j] + ord('0') );
            inc( i );
            inc( j );
            dec( n_before_dot );
        end;
    // Integer 0-padding
    if ( n_before_dot_pad0 > 0 ) then
    begin
        do_fillchar( str, i, n_before_dot_pad0, '0' );
        inc( i, n_before_dot_pad0 );
    end;
    //
    if ( frac_digits <> 0 ) then
    begin
        // Dot
        str[i] := '.';
        inc( i );
        // Pre-fraction 0-padding
        if ( n_after_dot_pad0 > 0 ) then
        begin
            do_fillchar( str, i, n_after_dot_pad0, '0' );
            inc( i, n_after_dot_pad0 );
        end;
        // Fraction significant digits
        if rounded then
            while ( n_after_dot > 0 ) do
            begin
                str[i] := char( temp_round[j] + ord('0') );
                inc( i );
                inc( j );
                dec( n_after_dot );
            end
        else
            while ( n_after_dot > 0 ) do
            begin
                str[i] := char( digits[j] + ord('0') );
                inc( i );
                inc( j );
                dec( n_after_dot );
            end;
        // Tail 0-padding
        if ( n_tail_pad0 > 0 ) then
        begin
            do_fillchar( str, i, n_tail_pad0, '0' );
{$ifdef grisu1_debug}
            inc( i, n_tail_pad0 );
{$endif grisu1_debug}
        end;
    end;
    //
{$ifdef grisu1_debug}
    assert( i = len + 1 );
{$endif grisu1_debug}
    try_return_fixed := true
end;

(*-------------------------------------------------------
 | return_exponential [local]
 |
 | Formats the number in the exponential representation.
 |
 *-------------------------------------------------------*)
procedure return_exponential( out str: shortstring; minus: boolean; const digits: TAsciiDigits; n_digits_have, n_digits_req, d_exp, n_digits_exp, min_width: integer );
var
    e_minus: boolean;
    i, j, len, n_exp, n_spaces, n_spaces_max: integer;
    buf_exp: TAsciiDigits;
begin
{$ifdef grisu1_debug}
    assert( n_digits_have >= 0 );
    assert( n_digits_have <= n_digits_req );
    assert( min_width <= C_MAX_WIDTH );
{$endif grisu1_debug}
    // Prepare exponent
    e_minus := ( d_exp < 0 );
    if e_minus then
        d_exp := - d_exp;
    n_exp := gen_digits_32( buf_exp, 0, d_exp, false );
    if ( n_exp <= n_digits_exp ) then
        len := n_digits_exp
    else
        len := n_exp;
    // Estimate string length
    inc( len, 1{sign} + n_digits_req + 1{E} + 1{E-sign} );
    if ( n_digits_req > 1 ) then
        inc( len ); // dot
    // Calculate space-padding length
    n_spaces_max := C_MAX_WIDTH - len;
    n_spaces := min_width - len;
    if ( n_spaces > n_spaces_max ) then
        n_spaces := n_spaces_max;
    if ( n_spaces > 0 ) then
        inc( len, n_spaces );
    // Allocate storage
    SetLength( str, len );
    i := 1;
    // Leading spaces
    if ( n_spaces > 0 ) then
    begin
        do_fillchar( str, i, n_spaces, ' ' );
        inc( i, n_spaces );
    end;
    // Sign
    if minus then
        str[i] := '-'
    else
        str[i] := ' ';
    inc( i );
    // Integer part
    if ( n_digits_have > 0 ) then
        str[i] := char( digits[0] + ord('0') )
    else
        str[i] := '0';
    inc( i );
    // Dot
    if ( n_digits_req > 1 ) then
    begin
        str[i] := '.';
        inc( i );
    end;
    // Fraction significant digits
    j := 1;
    while ( j < n_digits_have ) and ( j < n_digits_req ) do
    begin
        str[i] := char( digits[j] + ord('0') );
        inc( i );
        inc( j );
    end;
    // Fraction 0-padding
    j := n_digits_req - j;
    if ( j > 0 ) then
    begin
        do_fillchar( str, i, j, '0' );
        inc( i, j );
    end;
    // Exponent designator
    str[i] := 'E';
    inc( i );
    // Exponent sign
    if e_minus then
        str[i] := '-'
    else
        str[i] := '+';
    inc( i );
    // Exponent 0-padding
    j := n_digits_exp - n_exp;
    if ( j > 0 ) then
    begin
        do_fillchar( str, i, j, '0' );
        inc( i, j );
    end;
    // Exponent digits
    for j := 0 to n_exp - 1 do
    begin
        str[i] := char( buf_exp[j] + ord('0') );
        inc( i );
    end;
{$ifdef grisu1_debug}
    assert( i = len + 1 );
{$endif grisu1_debug}
end;

(*-------------------------------------------------------
 | return_special [local]
 |
 | This routine formats one of special results.
 |
 *-------------------------------------------------------*)
procedure return_special(  out str: shortstring; sign: integer; const spec: shortstring; min_width: integer );
var
    i, slen, len, n_spaces, n_spaces_max: integer;
begin
    slen := length(spec);
    if ( sign = 0 ) then
        len := slen
    else
        len := slen + 1;
    n_spaces_max := C_MAX_WIDTH - len;
    // Calculate space-padding length
    n_spaces := min_width - len;
    if ( n_spaces > n_spaces_max ) then
        n_spaces := n_spaces_max;
    if ( n_spaces > 0 ) then
        inc( len, n_spaces );
    // Allocate storage
    SetLength( str, len );
    i := 1;
    // Leading spaces
    if ( n_spaces > 0 ) then
    begin
        do_fillchar( str, i, n_spaces, ' ' );
        inc( i, n_spaces );
    end;
    // Sign
    if ( sign <> 0 ) then
    begin
        if ( sign > 0 ) then
            str[i] := '+'
        else
            str[i] := '-';
        inc( i );
    end;
    // Special
    while slen>0 do
      begin
        str[i+slen-1] := spec[slen];
        dec(slen);
      end;
end;

{$if defined(VALREAL_80) or defined(VALREAL_128)}
(*-------------------------------------------------------
 | u128_div_u64_to_u64 [local]
 |
 | Divides unsigned 128-bit integer by unsigned 64-bit integer.
 | Returns 64-bit quotient and reminder.
 |
 | This routine is used here only for splitting specially prepared unsigned
 | 128-bit integer into two 64-bit ones before converting it to ASCII.
 |
 *-------------------------------------------------------*)
function u128_div_u64_to_u64( const xh, xl: qword; const y: qword; out quotient, reminder: qword ): boolean;
var
    b,                // Number base
    v,                // Norm. divisor
    un1, un0,         // Norm. dividend LSD's
    vn1, vn0,         // Norm. divisor digits
    q1, q0,           // Quotient digits
    un64, un21, un10, // Dividend digit pairs
    rhat: qword;      // A remainder
    s: integer;       // Shift amount for norm
begin    
    // Overflow check
    if ( xh >= y ) then
    begin
        u128_div_u64_to_u64 := false;
        exit;
    end;
    // Count leading zeros
    s := 63 - BSRqword( y ); // 0 <= s <= 63
    // Normalize divisor
    v := y shl s;
    // Break divisor up into two 32-bit digits
    vn1 := hi(v);
    vn0 := lo(v);
    // Shift dividend left
    un64 := xh shl s;
    if ( s > 0 ) then
        un64 := un64 or ( xl shr ( 64 - s ) );
    un10 := xl shl s;
    // Break right half of dividend into two digits
    un1 := hi(un10);
    un0 := lo(un10);
    // Compute the first quotient digit, q1
    q1 := un64 div vn1;
    rhat := un64 - q1 * vn1;
    b := qword(1) shl 32; // Number base
    while ( q1 >= b ) or ( q1 * vn0 > b * rhat + un1 ) do
    begin
        dec( q1 );
        inc( rhat, vn1 );
        if rhat >= b then
            break;
    end;
    // Multiply and subtract
    un21 := un64 * b + un1 - q1 * v;
    // Compute the second quotient digit, q0
    q0 := un21 div vn1;
    rhat := un21 - q0 * vn1;
    while ( q0 >= b ) or ( q0 * vn0 > b * rhat + un0 ) do
    begin
        dec( q0 );
        inc( rhat, vn1 );
        if ( rhat >= b ) then
            break;
    end;
    // Result
    reminder := ( un21 * b + un0 - q0 * v ) shr s;
    quotient := q1 * b + q0;
    u128_div_u64_to_u64 := true;
end;
{$endif VALREAL_80 | VALREAL_128}

(*-------------------------------------------------------
 | count_leading_zero [local]
 |
 | Counts number of 0-bits at most significant bit position.
 |
 *-------------------------------------------------------*)
{$ifdef VALREAL_32}
function count_leading_zero( const X: dword ): integer; {$ifdef grisu1_inline}inline;{$endif}
begin
    count_leading_zero := 31 - BSRdword( X );
end;
{$else not VALREAL_32}
function count_leading_zero( const X: qword ): integer; {$ifdef grisu1_inline}inline;{$endif}
begin
    count_leading_zero := 63 - BSRqword( X );
end;
{$endif VALREAL_*}

{$if defined(VALREAL_80) or defined(VALREAL_128)}
(*-------------------------------------------------------
 | make_frac_mask [local]
 |
 | Makes DIY_FP fractional part mask:
 |     result := ( 1 shl one.e ) - 1
 |
 *-------------------------------------------------------*)
{$ifdef VALREAL_80}
procedure make_frac_mask( out h: dword; out l: qword; one_e: integer ); {$ifdef grisu1_inline}inline;{$endif}
{$else VALREAL_128}
procedure make_frac_mask( out h, l: qword; one_e: integer ); {$ifdef grisu1_inline}inline;{$endif}
{$endif VALREAL_*}
begin
  {$ifdef grisu1_debug}
    assert( one_e <= 0 );
    assert( one_e > - ( sizeof( l ) + sizeof( h ) ) * 8 );
  {$endif grisu1_debug}
    if ( one_e <= - 64 ) then
    begin
        l := qword( -1 );
        h := ( {$ifdef VALREAL_128} qword {$else} dword {$endif} ( 1 ) shl ( - one_e - 64 ) ) - 1;
    end
    else
    begin
        l := ( qword( 1 ) shl ( - one_e ) ) - 1;
        h := 0;
    end;
end;
{$endif VALREAL_80 | VALREAL_128}

(*-------------------------------------------------------
 | k_comp [local]
 |
 | Calculates the exp10 of a factor required to bring the binary exponent
 | of the original number into selected [ alpha .. gamma ] range:
 |     result := ceiling[ ( alpha - e ) * log10(2) ]
 |
 *-------------------------------------------------------*)
function k_comp( e, alpha{, gamma}: integer ): integer;
{$ifdef fpc_softfpu_implementation}
///////////////
//
// Assuming no HardFloat available.
// Note: using softfpu here significantly slows down overall
// conversion performance, so we use integers.
//
const
    D_LOG10_2: TDIY_FP = // log10(2) = 0.301029995663981195213738894724493027
      {$ifdef VALREAL_32}
        ( f: dword($9A209A85); e: -33 );
      {$endif}
      {$ifdef VALREAL_64}
        ( f: qword($9A209A84FBCFF799); e: -65 );
      {$endif}
      {$ifdef VALREAL_80}
        ( f: qword($FBCFF7988F8959AC); fh: dword($9A209A84); e: -97 );
      {$endif}
      {$ifdef VALREAL_128}
        ( fh: qword($9A209A84FBCFF798); f: qword($8F8959AC0B7C9178); e: -129 );
      {$endif}
var
    x, n: integer;
    y, z: TDIY_FP;
  {$ifdef VALREAL_32}
    mask_one: dword;
  {$else not VALREAL_32}
    mask_one: qword;
  {$endif}
  {$ifdef VALREAL_80}
    mask_oneh: dword;
  {$endif}
  {$ifdef VALREAL_128}
    mask_oneh: qword;
  {$endif}
    plus, round_up: boolean;
begin
    x := alpha - e;
    if ( x = 0 ) then
    begin
        k_comp := 0;
        exit;
    end;
    plus := ( x > 0 );
    if plus then
        y.f := x
    else
        y.f := - x;
    round_up := plus;
    n := C_DIY_FP_Q - 1 - BSRdword( y.f );
  {$if defined(VALREAL_32) or defined(VALREAL_64)}
    y.f := y.f shl n;
  {$else VALREAL_80 | VALREAL_128}
    y.fh := 0;
    diy_util_shl( y.fh, y.f, n );
  {$endif VALREAL_*}
    y.e := - n;
    z := diy_fp_multiply( y, D_LOG10_2, false );
    if ( z.e <= - C_DIY_FP_Q ) then
    begin
        round_up := plus and ( 0 <>
  {$if defined(VALREAL_32) or defined(VALREAL_64)}
          z.f
  {$else VALREAL_80 | VALREAL_128}
          z.f or z.fh
  {$endif}
        );
        n := 0;
    end
    else
    begin
        if plus then
        begin
          {$if defined(VALREAL_32) or defined(VALREAL_64)}
            mask_one := ( {$ifdef VALREAL_64} qword {$else} dword {$endif} ( 1 ) shl ( - z.e ) ) - 1;
            round_up := ( z.f and mask_one <> 0 );
          {$else VALREAL_80 | VALREAL_128}
            make_frac_mask( mask_oneh, mask_one, z.e );
            round_up := ( z.f and mask_one <> 0 ) or ( z.fh and mask_oneh <> 0 );
          {$endif VALREAL_*}
        end;
      {$if defined(VALREAL_32) or defined(VALREAL_64)}
        n := z.f shr ( - z.e );
      {$else VALREAL_80 | VALREAL_128}
        diy_util_shr( z.fh, z.f, - z.e );
        n := z.f;
      {$endif VALREAL_*}
    end;
    if not plus then
        n := - n;
    if round_up then
        k_comp := n + 1
    else
        k_comp := n;
end;
{$else not fpc_softfpu_implementation}
///////////////
//
// HardFloat implementation
//
{$if defined(SUPPORT_SINGLE) and defined(VALREAL_32)}
// If available, use single math for VALREAL_32
var
    dexp: single;
const
    D_LOG10_2: single =
{$elseif defined(SUPPORT_DOUBLE) and not defined(VALREAL_32)}
// If available, use double math for all types >VALREAL_32
var
    dexp: double;
const
    D_LOG10_2: double =
{$else}
// Use native math
var
    dexp: ValReal;
const
    D_LOG10_2: ValReal =
{$endif}
    0.301029995663981195213738894724493027; // log10(2)
var
    x, n: integer;
begin
    x := alpha - e;
    dexp := x * D_LOG10_2;
    // ceil( dexp )
    n := trunc( dexp );
    if ( x > 0 ) then
        if ( dexp <> n ) then
            inc( n ); // round-up
    k_comp := n;
end;
{$endif fpc_softfpu_implementation}

(****************************************************************************)
var
    w, D: TDIY_FP;
    c_mk: TDIY_FP_Power_of_10;
    n, mk, dot_pos, n_digits_exp, n_digits_need, n_digits_have: integer;
    n_digits_req, n_digits_sci: integer;
    minus: boolean;
  {$ifndef VALREAL_32}
    fl, one_maskl: qword;
  {$endif not VALREAL_32}
  {$ifdef VALREAL_80}
    templ: qword;
    fh, one_maskh, temph: dword;
  {$endif VALREAL_80}
  {$ifdef VALREAL_128}
    templ: qword;
    fh, one_maskh, temph: qword;
  {$endif VALREAL_128}
    one_e: integer;
    one_mask, f: dword;
    buf: TAsciiDigits;

begin

    // Limit parameters
    if ( frac_digits > 216 ) then
        frac_digits := 216; // Delphi compatible
    if ( min_width <= C_NO_MIN_WIDTH ) then
        min_width := -1 // no minimal width
    else
        if ( min_width < 0 ) then
            min_width := 0 // minimal width is as short as possible
        else
            if ( min_width > C_MAX_WIDTH ) then
                min_width := C_MAX_WIDTH;

    // Format profile: select "n_digits_need" and "n_digits_exp"
    n_digits_req := float_format[real_type].nDig_mantissa;
    n_digits_exp := float_format[real_type].nDig_exp10;
    // number of digits to be calculated by Grisu
    n_digits_need := float_format[RT_NATIVE].nDig_mantissa;
    if ( n_digits_req < n_digits_need ) then
        n_digits_need := n_digits_req;
    // number of mantissa digits to be printed in exponential notation
    if ( min_width < 0 ) then
        n_digits_sci := n_digits_req
    else
    begin
        n_digits_sci := min_width - 1{sign} - 1{dot} - 1{E} - 1{E-sign} - n_digits_exp;
        if ( n_digits_sci < 2 ) then
            n_digits_sci := 2; // at least 2 digits
        if ( n_digits_sci > n_digits_req ) then
            n_digits_sci := n_digits_req; // at most requested by real_type
    end;

    // Float -> DIY_FP
    w := unpack_float( v, minus );

    // Handle Zero
    if ( w.e = 0 ) and ( w.f {$ifdef VALREAL_128} or w.fh {$endif} = 0 ) then
    begin
        buf[0] := 0; // to avoid "warning: uninitialized"
        if ( frac_digits >= 0 ) then
            if try_return_fixed( str, minus, buf, 0, 1, min_width, frac_digits ) then
                exit
          {$ifdef grisu1_debug}
            else
                assert( FALSE ) // should never fail with these arguments
          {$endif grisu1_debug};
        return_exponential( str, minus, buf, 0, n_digits_sci, 0, n_digits_exp, min_width );
        exit;
    end;

  {$ifdef VALREAL_80}
    // Handle non-normals
    if ( w.e <> 0 ) and ( w.e <> C_EXP2_SPECIAL ) then
        if ( w.f and C_MANT2_INTEGER = 0 ) then
        begin
            // -> QNaN
            w.f := qword(-1);
            w.e := C_EXP2_SPECIAL;
        end;
  {$endif VALREAL_80}

    // Handle specials
    if ( w.e = C_EXP2_SPECIAL ) then
    begin
        if ( min_width < 0 ) then
            // backward compat..
            min_width := float_format[real_type].nDig_mantissa + float_format[real_type].nDig_exp10 + 4;
        n := 1 - ord(minus) * 2; // default special sign [-1|+1]
      {$if defined(VALREAL_32) or defined(VALREAL_64)}
        if ( w.f = 0 ) then
      {$endif VALREAL_32 | VALREAL_64}
      {$ifdef VALREAL_80}
        if ( w.f = qword(C_MANT2_INTEGER) ) then
      {$endif VALREAL_80}
      {$ifdef VALREAL_128}
        if ( w.fh or w.f = 0 ) then
      {$endif VALREAL_128}
        begin
           // Inf
           return_special( str, n, C_STR_INF, min_width );
        end
        else
        begin
           // NaN [also pseudo-NaN, pseudo-Inf, non-normal for floatx80]
       {$ifdef GRISU1_F2A_NAN_SIGNLESS}
           n := 0;
       {$endif}
       {$ifndef GRISU1_F2A_NO_SNAN}
         {$ifdef VALREAL_128}
           if ( w.fh and ( C_MANT2_INTEGER_H shr 1 ) = 0 ) then
         {$else}
           if ( w.f and ( C_MANT2_INTEGER shr 1 ) = 0 ) then
         {$endif}
               return_special( str, n, C_STR_SNAN, min_width )
           else
       {$endif GRISU1_F2A_NO_SNAN}
               return_special( str, n, C_STR_QNAN, min_width );
        end;
        exit;
    end;

    // Handle denormals
    if ( w.e <> 0 ) then
    begin
        // normal
    {$ifdef VALREAL_128}
        w.fh := w.fh or C_MANT2_INTEGER_H;
    {$else not VALREAL_128}
      {$ifndef VALREAL_80}
        w.f := w.f or C_MANT2_INTEGER;
      {$endif not VALREAL_80}
    {$endif VALREAL_*}
        n := C_DIY_FP_Q - C_FRAC2_BITS - 1;
    end
    else
    begin
        // denormal
    {$ifdef VALREAL_128}
        if ( w.fh = 0 ) then
            n := count_leading_zero( w.f ) + 64
        else
            n := count_leading_zero( w.fh );
    {$else not VALREAL_128}
      {$ifdef VALREAL_80}
        // also handle pseudo-denormals
        n := count_leading_zero( w.f ) + 32;
      {$else VALREAL_32 | VALREAL_64}
        n := count_leading_zero( w.f );
      {$endif VALREAL_*}
    {$endif VALREAL_*}
        inc( w.e );
    end;

    // Final normalization
  {$if defined(VALREAL_32) or defined(VALREAL_64)}
    w.f := w.f shl n;
  {$else VALREAL_80 | VALREAL_128}
    diy_util_shl( w.fh, w.f, n );
  {$endif VALREAL_*}
    dec( w.e, C_EXP2_BIAS + n + C_FRAC2_BITS );

    //
    // 1. Find the normalized "c_mk = f_c * 2^e_c" such that "alpha <= e_c + e_w + q <= gamma"
    // 2. Define "V = D * 10^k": multiply the input number by "c_mk", do not normalize to land into [ alpha .. gamma ]
    // 3. Generate digits ( n_digits_need + "round" )
    //

    if ( C_GRISU_ALPHA <= w.e ) and ( w.e <= C_GRISU_GAMMA ) then
    begin
        // no scaling required
        D := w;
        c_mk.e10 := 0;
    end
    else
    begin
        mk := k_comp( w.e, C_GRISU_ALPHA{, C_GRISU_GAMMA} );
        diy_fp_cached_power10( mk, c_mk );
        // Let "D = f_D * 2^e_D := w (*) c_mk"
        if c_mk.e10 = 0 then
            D := w
        else
            D := diy_fp_multiply( w, c_mk.c, FALSE );
    end;

{$ifdef grisu1_debug}
    assert( ( C_GRISU_ALPHA <= D.e ) and ( D.e <= C_GRISU_GAMMA ) );
{$endif grisu1_debug}

    // Generate digits: integer part
{$ifdef grisu1_debug}
  {$ifdef VALREAL_80}
    assert( D.e <= 32 );
  {$else not VALREAL_80}
    assert( D.e <= 0 );
  {$endif VALREAL_*}
{$endif grisu1_debug}

  {$ifdef VALREAL_32}
    n_digits_have := gen_digits_32( buf, 0, D.f shr ( - D.e ) );
  {$endif VALREAL_32}
  
  {$ifdef VALREAL_64}
    n_digits_have := gen_digits_64( buf, 0, D.f shr ( - D.e ) );
  {$endif VALREAL_64}
  
  {$ifdef VALREAL_80}
    fl := D.f;
    fh := D.fh;
    if ( D.e > 0 ) then
    begin
        templ := ( qword(fh) shl D.e ) and qword($FFFFFFFF00000000);
        diy_util_shl( fh, fl, D.e );
        inc( templ, fh );
    end
    else
    begin
        diy_util_shr( fh, fl, - D.e );
        templ := fh;
    end;
  {$endif VALREAL_80}

  {$ifdef VALREAL_128}
    fl := D.f;
    templ := D.fh;
    diy_util_shr( templ, fl, - D.e );
  {$endif VALREAL_128}

  {$if defined(VALREAL_80) or defined(VALREAL_128)}
    if ( templ = 0 ) then
        n_digits_have := gen_digits_64( buf, 0, fl )
    else
    begin
        if not u128_div_u64_to_u64( templ, fl, qword(10000000000000000000), templ, fl ) then
{$ifdef grisu1_debug}
            assert( FALSE ) // never overflows by design
{$endif grisu1_debug};
        n_digits_have := gen_digits_64( buf, 0, templ );
        inc( n_digits_have, gen_digits_64( buf, n_digits_have, fl, n_digits_have > 0 ) );
    end;
  {$endif VALREAL_80 | VALREAL_128}

    dot_pos := n_digits_have;

    // Generate digits: fractional part
    f := 0; // "sticky" digit
    if ( D.e < 0 ) then
    repeat
        // MOD by ONE
        one_e := D.e;
      {$ifdef VALREAL_32}
        one_mask := dword( 1 ) shl ( - D.e ) - 1;
        f := D.f and one_mask;
      {$endif VALREAL_32}
      {$ifdef VALREAL_64}
        one_maskl := qword( 1 ) shl ( - D.e ) - 1;
        fl := D.f and one_maskl;
      {$endif VALREAL_64}
      {$if defined(VALREAL_80) or defined(VALREAL_128)}
        make_frac_mask( one_maskh, one_maskl, D.e );
        fl := D.f and one_maskl;
        fh := D.fh and one_maskh;

        // 128/96-bit loop
        while ( one_e < -61 ) and ( n_digits_have < n_digits_need + 1 ) and ( fl or fh <> 0 ) do
        begin
            // f := f * 5;
            templ := fl;
            temph := fh;
            diy_util_shl( fh, fl, 2 );
            diy_util_add( fh, fl, temph, templ );
            // one := one / 2
            diy_util_shr( one_maskh, one_maskl, 1 );
            inc( one_e );
            // DIV by one
            templ := fl;
            temph := fh;
            diy_util_shr( temph, templ, - one_e );
            buf[ n_digits_have ] := lo(templ);
            // MOD by one
            fl := fl and one_maskl;
            fh := fh and one_maskh;
            // next
            inc( n_digits_have );
        end;
        if ( n_digits_have >= n_digits_need + 1 ) then
        begin
            // only "sticky" digit remains
            f := ord( fl or fh <> 0 );
            break;
        end;
      {$endif VALREAL_80 | VALREAL_128}

      {$ifndef VALREAL_32}
        // 64-bit loop
        while ( one_e < -29 ) and ( n_digits_have < n_digits_need + 1 ) and ( fl <> 0 ) do
        begin
            // f := f * 5;
            inc( fl, fl shl 2 );
            // one := one / 2
            one_maskl := one_maskl shr 1;
            inc( one_e );
            // DIV by one
            buf[ n_digits_have ] := fl shr ( - one_e );
            // MOD by one
            fl := fl and one_maskl;
            // next
            inc( n_digits_have );
        end;
        if ( n_digits_have >= n_digits_need + 1 ) then
        begin
            // only "sticky" digit remains
            f := ord( fl <> 0 );
            break;
        end;
        one_mask := lo(one_maskl);
        f := lo(fl);
      {$endif not VALREAL_32}

        // 32-bit loop
        while ( n_digits_have < n_digits_need + 1 ) and ( f <> 0 ) do
        begin
            // f := f * 5;
            inc( f, f shl 2 );
            // one := one / 2
            one_mask := one_mask shr 1;
            inc( one_e );
            // DIV by one
            buf[ n_digits_have ] := f shr ( - one_e );
            // MOD by one
            f := f and one_mask;
            // next
            inc( n_digits_have );
        end;

    until true;

    // Append "sticky" digit if any
    if ( f <> 0 ) and ( n_digits_have >= n_digits_need + 1 ) then
    begin
        // single "<>0" digit is enough
        n_digits_have := n_digits_need + 2;
        buf[ n_digits_need + 1 ] := 1;
    end;

    // Round to n_digits_need using "roundTiesToEven"
    if ( n_digits_have > n_digits_need ) then
        inc( dot_pos, round_digits( buf, n_digits_have, n_digits_need ) );

    // Generate output
    if ( frac_digits >= 0 ) then
        if try_return_fixed( str, minus, buf, n_digits_have, dot_pos - c_mk.e10, min_width, frac_digits ) then
            exit;
    if ( n_digits_have > n_digits_sci ) then
        inc( dot_pos, round_digits( buf, n_digits_have, n_digits_sci {$ifdef GRISU1_F2A_HALF_ROUNDUP}, false {$endif} ) );
    return_exponential( str, minus, buf, n_digits_have, n_digits_sci, dot_pos - c_mk.e10 - 1, n_digits_exp, min_width );

end;

(****************************************************************************)

{$ifndef fpc_softfpu_implementation}
procedure str_real_iso( len, f: longint; d: ValReal; real_type: treal_type; out s: string );
var
    i: integer;
begin
    str_real( len, f, d, real_type, s );
    for i := length( s ) downto 1 do
        if ( s[i] ='E' ) then
        begin
            s[i] := 'e';
            break; // only one "E" expected
        end;
end;
{$endif not fpc_softfpu_implementation}

(*==========================================================================*
 *                                                                          *
 *                              ASCII -> Float                              *
 *                                                                          *
 *==========================================================================*)

function val_real( const src: shortstring; out err_pos: ValSInt ): ValReal;

{$define VALREAL_PACK}
{$i flt_pack.inc}

{ NOTE: C_MAX_DIGITS_ACCEPTED should fit into unsigned integer which forms DIY_FP }
const
{$ifdef VALREAL_32}
    C_MAX_DIGITS_ACCEPTED = 9;
    C_EXP10_OVER = 100;
    C_DIY_FP_Q   = 32;
    C_FRAC2_BITS = 23;
    C_EXP2_BIAS  = 127;
{$endif VALREAL_32}
{$ifdef VALREAL_64}
    C_MAX_DIGITS_ACCEPTED = 19;
    C_EXP10_OVER = 1000;
    C_DIY_FP_Q   = 64;
    C_FRAC2_BITS = 52;
    C_EXP2_BIAS  = 1023;
{$endif VALREAL_64}
{$ifdef VALREAL_80}
    C_MAX_DIGITS_ACCEPTED = 28;
    C_EXP10_OVER = 10000;
    C_DIY_FP_Q   = 96;
    C_FRAC2_BITS = 63;
    C_EXP2_BIAS  = 16383;
{$endif VALREAL_80}
{$ifdef VALREAL_128}
    C_MAX_DIGITS_ACCEPTED = 38;
    C_EXP10_OVER = 10000;
    C_DIY_FP_Q   = 128;
    C_FRAC2_BITS = 112;
    C_EXP2_BIAS  = 16383;
{$endif VALREAL_128}

(****************************************************************************)
    // handy const
    C_EXP2_SPECIAL   = C_EXP2_BIAS * 2 + 1;
    C_DIY_SHR_TO_FLT = C_DIY_FP_Q - 1 - C_FRAC2_BITS;
    C_DIY_EXP_TO_FLT = C_DIY_FP_Q - 1 + C_EXP2_BIAS;
    C_DIY_ROUND_BIT  = 1 shl ( C_DIY_SHR_TO_FLT - 1 );
    C_DIY_ROUND_MASK = ( C_DIY_ROUND_BIT shl 2 ) - 1;
{$ifdef VALREAL_128}
    C_FLT_INT_BITh   = qword(1) shl ( C_FRAC2_BITS - 64 );
    C_FLT_FRAC_MASKh = C_FLT_INT_BITh - 1;
{$else not VALREAL_128}
  {$ifdef VALREAL_32}
    C_FLT_INT_BIT    = dword(1) shl C_FRAC2_BITS;
    C_FLT_FRAC_MASK  = C_FLT_INT_BIT - 1;
  {$else VALREAL_64 | VALREAL_80}
    C_FLT_INT_BIT    = qword(1) shl C_FRAC2_BITS;
    C_FLT_FRAC_MASK  = qword(C_FLT_INT_BIT) - 1;
  {$endif VALREAL_*}
{$endif VALREAL_*}

    // specials
{$ifdef VALREAL_32}
    C_FLT_MANT_INF  = dword($00000000);
  {$ifndef GRISU1_A2F_NO_SNAN}
    C_FLT_MANT_SNAN = dword($00200000);
  {$endif GRISU1_A2F_NO_SNAN}
    C_FLT_MANT_QNAN = dword($00400000);
{$endif VALREAL_32}
{$ifdef VALREAL_64}
    C_FLT_MANT_INF  = qword($0000000000000000);
  {$ifndef GRISU1_A2F_NO_SNAN}
    C_FLT_MANT_SNAN = qword($0004000000000000);
  {$endif GRISU1_A2F_NO_SNAN}
    C_FLT_MANT_QNAN = qword($0008000000000000);
{$endif VALREAL_64}
{$ifdef VALREAL_80}
    C_FLT_MANT_INF  = qword($8000000000000000);
  {$ifndef GRISU1_A2F_NO_SNAN}
    C_FLT_MANT_SNAN = qword($A000000000000000);
  {$endif GRISU1_A2F_NO_SNAN}
    C_FLT_MANT_QNAN = qword($C000000000000000);
{$endif VALREAL_80}
{$ifdef VALREAL_128}
    C_FLT_MANT_INFh  = qword($0000000000000000);
    C_FLT_MANT_INF   = qword($0000000000000000);
  {$ifndef GRISU1_A2F_NO_SNAN}
    C_FLT_MANT_SNANh = qword($0000400000000000);
    C_FLT_MANT_SNAN  = qword($0000000000000000);
  {$endif GRISU1_A2F_NO_SNAN}
    C_FLT_MANT_QNANh = qword($0000800000000000);
    C_FLT_MANT_QNAN  = qword($0000000000000000);
{$endif VALREAL_128}

(*-------------------------------------------------------
 | factor_10_inexact [local]
 |
 | Calculates an arbitrary normalized power of 10 required for final scaling.
 | The result of this calculation may be off by several ulp's from exact.
 |
 | Returns an overflow/underflow status:
 |    "<0": underflow
 |    "=0": ok
 |    ">0": overflow
 |
 *-------------------------------------------------------*)
function factor_10_inexact( const exp10: integer; out C: TDIY_FP_Power_of_10 ): integer;
const
{$ifdef VALREAL_32}
    factor: array [ 0 .. 7 ] of TDIY_FP_Power_of_10 = (
        ( c: ( f: $80000000; e: -31); e10:   0 ),
        ( c: ( f: $CCCCCCCD; e: -35); e10:  -1 ),
        ( c: ( f: $A3D70A3D; e: -38); e10:  -2 ),
        ( c: ( f: $83126E98; e: -41); e10:  -3 ),
        ( c: ( f: $D1B71759; e: -45); e10:  -4 ),
        ( c: ( f: $A7C5AC47; e: -48); e10:  -5 ),
        ( c: ( f: $8637BD06; e: -51); e10:  -6 ),
        ( c: ( f: $D6BF94D6; e: -55); e10:  -7 )
    );
{$endif VALREAL_32}
{$ifdef VALREAL_64}
    factor: array [ 0 .. 17 ] of TDIY_FP_Power_of_10 = (
        ( c: ( f: qword($8000000000000000); e:  -63); e10:   0 ),
        ( c: ( f: qword($CCCCCCCCCCCCCCCD); e:  -67); e10:  -1 ),
        ( c: ( f: qword($A3D70A3D70A3D70A); e:  -70); e10:  -2 ),
        ( c: ( f: qword($83126E978D4FDF3B); e:  -73); e10:  -3 ),
        ( c: ( f: qword($D1B71758E219652C); e:  -77); e10:  -4 ),
        ( c: ( f: qword($A7C5AC471B478423); e:  -80); e10:  -5 ),
        ( c: ( f: qword($8637BD05AF6C69B6); e:  -83); e10:  -6 ),
        ( c: ( f: qword($D6BF94D5E57A42BC); e:  -87); e10:  -7 ),
        ( c: ( f: qword($ABCC77118461CEFD); e:  -90); e10:  -8 ),
        ( c: ( f: qword($89705F4136B4A597); e:  -93); e10:  -9 ),
        ( c: ( f: qword($DBE6FECEBDEDD5BF); e:  -97); e10: -10 ),
        ( c: ( f: qword($AFEBFF0BCB24AAFF); e: -100); e10: -11 ),
        ( c: ( f: qword($8CBCCC096F5088CC); e: -103); e10: -12 ),
        ( c: ( f: qword($E12E13424BB40E13); e: -107); e10: -13 ),
        ( c: ( f: qword($B424DC35095CD80F); e: -110); e10: -14 ),
        ( c: ( f: qword($901D7CF73AB0ACD9); e: -113); e10: -15 ),
        ( c: ( f: qword($E69594BEC44DE15B); e: -117); e10: -16 ),
        ( c: ( f: qword($B877AA3236A4B449); e: -120); e10: -17 )
    );
{$endif VALREAL_64}
{$ifdef VALREAL_80}
    factor: array [ 0 .. 36 ] of TDIY_FP_Power_of_10 = (
        ( c: ( f: qword($0000000000000000); fh: dword($80000000); e:  -95 ); e10:   0 ),
        ( c: ( f: qword($CCCCCCCCCCCCCCCD); fh: dword($CCCCCCCC); e:  -99 ); e10:  -1 ),
        ( c: ( f: qword($70A3D70A3D70A3D7); fh: dword($A3D70A3D); e: -102 ); e10:  -2 ),
        ( c: ( f: qword($8D4FDF3B645A1CAC); fh: dword($83126E97); e: -105 ); e10:  -3 ),
        ( c: ( f: qword($E219652BD3C36113); fh: dword($D1B71758); e: -109 ); e10:  -4 ),
        ( c: ( f: qword($1B4784230FCF80DC); fh: dword($A7C5AC47); e: -112 ); e10:  -5 ),
        ( c: ( f: qword($AF6C69B5A63F9A4A); fh: dword($8637BD05); e: -115 ); e10:  -6 ),
        ( c: ( f: qword($E57A42BC3D329076); fh: dword($D6BF94D5); e: -119 ); e10:  -7 ),
        ( c: ( f: qword($8461CEFCFDC20D2B); fh: dword($ABCC7711); e: -122 ); e10:  -8 ),
        ( c: ( f: qword($36B4A59731680A89); fh: dword($89705F41); e: -125 ); e10:  -9 ),
        ( c: ( f: qword($BDEDD5BEB573440E); fh: dword($DBE6FECE); e: -129 ); e10: -10 ),
        ( c: ( f: qword($CB24AAFEF78F69A5); fh: dword($AFEBFF0B); e: -132 ); e10: -11 ),
        ( c: ( f: qword($6F5088CBF93F87B7); fh: dword($8CBCCC09); e: -135 ); e10: -12 ),
        ( c: ( f: qword($4BB40E132865A5F2); fh: dword($E12E1342); e: -139 ); e10: -13 ),
        ( c: ( f: qword($095CD80F538484C2); fh: dword($B424DC35); e: -142 ); e10: -14 ),
        ( c: ( f: qword($3AB0ACD90F9D3701); fh: dword($901D7CF7); e: -145 ); e10: -15 ),
        ( c: ( f: qword($C44DE15B4C2EBE68); fh: dword($E69594BE); e: -149 ); e10: -16 ),
        ( c: ( f: qword($36A4B44909BEFEBA); fh: dword($B877AA32); e: -152 ); e10: -17 ),
        ( c: ( f: qword($921D5D073AFF322E); fh: dword($9392EE8E); e: -155 ); e10: -18 ),
        ( c: ( f: qword($B69561A52B31E9E4); fh: dword($EC1E4A7D); e: -159 ); e10: -19 ),
        ( c: ( f: qword($92111AEA88F4BB1D); fh: dword($BCE50864); e: -162 ); e10: -20 ),
        ( c: ( f: qword($74DA7BEED3F6FC17); fh: dword($971DA050); e: -165 ); e10: -21 ),
        ( c: ( f: qword($BAF72CB15324C68B); fh: dword($F1C90080); e: -169 ); e10: -22 ),
        ( c: ( f: qword($95928A2775B7053C); fh: dword($C16D9A00); e: -172 ); e10: -23 ),
        ( c: ( f: qword($44753B52C4926A96); fh: dword($9ABE14CD); e: -175 ); e10: -24 ),
        ( c: ( f: qword($D3EEC5513A83DDBE); fh: dword($F79687AE); e: -179 ); e10: -25 ),
        ( c: ( f: qword($76589DDA95364AFE); fh: dword($C6120625); e: -182 ); e10: -26 ),
        ( c: ( f: qword($91E07E48775EA265); fh: dword($9E74D1B7); e: -185 ); e10: -27 ),
        ( c: ( f: qword($8300CA0D8BCA9D6E); fh: dword($FD87B5F2); e: -189 ); e10: -28 ),
        ( c: ( f: qword($359A3B3E096EE458); fh: dword($CAD2F7F5); e: -192 ); e10: -29 ),
        ( c: ( f: qword($5E14FC31A125837A); fh: dword($A2425FF7); e: -195 ); e10: -30 ),
        ( c: ( f: qword($4B43FCF480EACF95); fh: dword($81CEB32C); e: -198 ); e10: -31 ),
        ( c: ( f: qword($453994BA67DE18EE); fh: dword($CFB11EAD); e: -202 ); e10: -32 ),
        ( c: ( f: qword($D0FADD61ECB1AD8B); fh: dword($A6274BBD); e: -205 ); e10: -33 ),
        ( c: ( f: qword($DA624AB4BD5AF13C); fh: dword($84EC3C97); e: -208 ); e10: -34 ),
        ( c: ( f: qword($C3D07787955E4EC6); fh: dword($D4AD2DBF); e: -212 ); e10: -35 ),
        ( c: ( f: qword($697392D2DDE50BD2); fh: dword($AA242499); e: -215 ); e10: -36 )
    );
{$endif VALREAL_80}
{$ifdef VALREAL_128}
    factor: array [ 0 .. 36 ] of TDIY_FP_Power_of_10 = (
        ( c: ( fh: qword($8000000000000000); f: qword($0000000000000000); e: -127 ); e10:   0 ),
        ( c: ( fh: qword($CCCCCCCCCCCCCCCC); f: qword($CCCCCCCCCCCCCCCD); e: -131 ); e10:  -1 ),
        ( c: ( fh: qword($A3D70A3D70A3D70A); f: qword($3D70A3D70A3D70A4); e: -134 ); e10:  -2 ),
        ( c: ( fh: qword($83126E978D4FDF3B); f: qword($645A1CAC083126E9); e: -137 ); e10:  -3 ),
        ( c: ( fh: qword($D1B71758E219652B); f: qword($D3C36113404EA4A9); e: -141 ); e10:  -4 ),
        ( c: ( fh: qword($A7C5AC471B478423); f: qword($0FCF80DC33721D54); e: -144 ); e10:  -5 ),
        ( c: ( fh: qword($8637BD05AF6C69B5); f: qword($A63F9A49C2C1B110); e: -147 ); e10:  -6 ),
        ( c: ( fh: qword($D6BF94D5E57A42BC); f: qword($3D32907604691B4D); e: -151 ); e10:  -7 ),
        ( c: ( fh: qword($ABCC77118461CEFC); f: qword($FDC20D2B36BA7C3D); e: -154 ); e10:  -8 ),
        ( c: ( fh: qword($89705F4136B4A597); f: qword($31680A88F8953031); e: -157 ); e10:  -9 ),
        ( c: ( fh: qword($DBE6FECEBDEDD5BE); f: qword($B573440E5A884D1B); e: -161 ); e10: -10 ),
        ( c: ( fh: qword($AFEBFF0BCB24AAFE); f: qword($F78F69A51539D749); e: -164 ); e10: -11 ),
        ( c: ( fh: qword($8CBCCC096F5088CB); f: qword($F93F87B7442E45D4); e: -167 ); e10: -12 ),
        ( c: ( fh: qword($E12E13424BB40E13); f: qword($2865A5F206B06FBA); e: -171 ); e10: -13 ),
        ( c: ( fh: qword($B424DC35095CD80F); f: qword($538484C19EF38C94); e: -174 ); e10: -14 ),
        ( c: ( fh: qword($901D7CF73AB0ACD9); f: qword($0F9D37014BF60A10); e: -177 ); e10: -15 ),
        ( c: ( fh: qword($E69594BEC44DE15B); f: qword($4C2EBE687989A9B4); e: -181 ); e10: -16 ),
        ( c: ( fh: qword($B877AA3236A4B449); f: qword($09BEFEB9FAD487C3); e: -184 ); e10: -17 ),
        ( c: ( fh: qword($9392EE8E921D5D07); f: qword($3AFF322E62439FCF); e: -187 ); e10: -18 ),
        ( c: ( fh: qword($EC1E4A7DB69561A5); f: qword($2B31E9E3D06C32E5); e: -191 ); e10: -19 ),
        ( c: ( fh: qword($BCE5086492111AEA); f: qword($88F4BB1CA6BCF584); e: -194 ); e10: -20 ),
        ( c: ( fh: qword($971DA05074DA7BEE); f: qword($D3F6FC16EBCA5E03); e: -197 ); e10: -21 ),
        ( c: ( fh: qword($F1C90080BAF72CB1); f: qword($5324C68B12DD6338); e: -201 ); e10: -22 ),
        ( c: ( fh: qword($C16D9A0095928A27); f: qword($75B7053C0F178294); e: -204 ); e10: -23 ),
        ( c: ( fh: qword($9ABE14CD44753B52); f: qword($C4926A9672793543); e: -207 ); e10: -24 ),
        ( c: ( fh: qword($F79687AED3EEC551); f: qword($3A83DDBD83F52205); e: -211 ); e10: -25 ),
        ( c: ( fh: qword($C612062576589DDA); f: qword($95364AFE032A819D); e: -214 ); e10: -26 ),
        ( c: ( fh: qword($9E74D1B791E07E48); f: qword($775EA264CF55347E); e: -217 ); e10: -27 ),
        ( c: ( fh: qword($FD87B5F28300CA0D); f: qword($8BCA9D6E188853FC); e: -221 ); e10: -28 ),
        ( c: ( fh: qword($CAD2F7F5359A3B3E); f: qword($096EE45813A04330); e: -224 ); e10: -29 ),
        ( c: ( fh: qword($A2425FF75E14FC31); f: qword($A1258379A94D028D); e: -227 ); e10: -30 ),
        ( c: ( fh: qword($81CEB32C4B43FCF4); f: qword($80EACF948770CED7); e: -230 ); e10: -31 ),
        ( c: ( fh: qword($CFB11EAD453994BA); f: qword($67DE18EDA5814AF2); e: -234 ); e10: -32 ),
        ( c: ( fh: qword($A6274BBDD0FADD61); f: qword($ECB1AD8AEACDD58E); e: -237 ); e10: -33 ),
        ( c: ( fh: qword($84EC3C97DA624AB4); f: qword($BD5AF13BEF0B113F); e: -240 ); e10: -34 ),
        ( c: ( fh: qword($D4AD2DBFC3D07787); f: qword($955E4EC64B44E864); e: -244 ); e10: -35 ),
        ( c: ( fh: qword($AA242499697392D2); f: qword($DDE50BD1D5D0B9EA); e: -247 ); e10: -36 )
    );
{$endif VALREAL_128}
var
    i: integer;
    a, b: TDIY_FP_Power_of_10;
begin
    diy_fp_cached_power10( exp10, a );
    i := a.e10 - exp10;
    if ( i < 0 ) then
    begin
        factor_10_inexact := 1; // overflow
        exit;
    end;
    if ( i > high( factor ) ) then
    begin
        factor_10_inexact := -1; // underflow
        exit;
    end;
    b := factor[i];
{$ifdef grisu1_debug}
    assert( exp10 =  a.e10 + b.e10 );
{$endif grisu1_debug}
    if ( b.e10 = 0 ) then
       C := a
    else
    if ( a.e10 = 0 ) then
       C := b
    else
    begin
        C.c := diy_fp_multiply( a.c, b.c, TRUE );
        c.e10 := exp10;
    end;
    factor_10_inexact := 0; // no error
end;

(*-------------------------------------------------------
 | add_digit [local]
 |
 | This helper routine performs next digit addition:
 |     X := X * 10 + digit
 |
 *-------------------------------------------------------*)
{$ifdef VALREAL_80}
procedure add_digit( var h: dword; var l: qword; digit: byte ); {$ifdef grisu1_inline}inline;{$endif}
var
    temp1, temp2: qword;
begin
    //
    temp1 := lo(l);
    inc( temp1, ( temp1 shl 3 ) + temp1 + digit );
    //
    temp2 := h;
    temp2 := ( temp2 shl 32 ) + hi(l);
    inc( temp2, ( temp2 shl 3 ) + temp2 + hi(temp1) );
    //
    h := hi(temp2);
    l := ( temp2 shl 32 ) + lo(temp1);
    //
end;
{$endif VALREAL_80}
{$ifdef VALREAL_128}
procedure add_digit( var h, l: qword; digit: byte ); {$ifdef grisu1_inline}inline;{$endif}
var
    templ, temph, temp1, temp2: qword;
begin
    templ := l;
    temph := h;
    diy_util_shl( temph, templ, 3 );
    //
    temp1 := lo(l);
    inc( temp1, lo(templ) + temp1 + digit );
    //
    temp2 := hi(l);
    inc( temp2, hi(templ) + temp2 + hi(temp1) );
    //
    inc( h, temph + h + hi(temp2) );
    l := ( temp2 shl 32 ) + lo(temp1);
    //
end;
{$endif VALREAL_128}

(*-------------------------------------------------------
 | count_leading_zero [local] <<<duplicate>>>
 |
 | Counts number of 0-bits at most significant bit position.
 |
 *-------------------------------------------------------*)
{$if defined(VALREAL_32) or defined(VALREAL_80)}
function count_leading_zero( const X: dword ): integer; {$ifdef grisu1_inline}inline;{$endif}
begin
    count_leading_zero := 31 - BSRdword( X );
end;
{$endif VALREAL_32 | VALREAL_80}
{$ifndef VALREAL_32}
function count_leading_zero( const X: qword ): integer; {$ifdef grisu1_inline}inline;{$endif}
begin
    count_leading_zero := 63 - BSRqword( X );
end;
{$endif not VALREAL_32}

(*-------------------------------------------------------
 | match_special [local]
 |
 | Routine compares source string tail with the string representing
 | one of special values: Inf | QNaN | SNaN
 |
 *-------------------------------------------------------*)
function match_special( src_pos: integer; const src, spec: shortstring ): boolean;
var
    sl, xl: integer;
begin
    match_special := false;
    xl := length( src );
    sl := length( spec );
    if ( sl <> xl - src_pos + 1 ) then
        exit;
{$ifdef grisu1_debug}
    assert( sl > 0 );
{$endif grisu1_debug}
    repeat
        if ( UpCase( src[xl] ) <> UpCase( spec[sl] ) ) then
            exit;
        dec( xl );
        dec( sl );
    until sl <= 0;
    match_special := true;
end;

(****************************************************************************)
var
    a: char;
    mantissa, bit_round, bit_round_mask: {$ifdef VALREAL_32} dword {$else} qword {$endif};
{$ifdef VALREAL_80}
    mantissa_h: dword;
{$endif}
{$ifdef VALREAL_128}
    mantissa_h, bit_round_h, bit_round_mask_h: qword;
{$endif}
    dig_num, exp10, overflow, n, src_pos, src_len: integer;
    exp_read, exp_temp: longint;
    b, dig_round, dig_sticky: byte;
    minus, exp_minus, special: boolean;
    C: TDIY_FP_Power_of_10;
    D: TDIY_FP;

begin

    err_pos := 1;
    src_pos := 1;
    src_len := length(src);

    // Pre-initialize result
{$ifdef GRISU1_A2F_ERROR_RET0}
    pack_float( val_real, false, 0, {$ifdef VALREAL_128} 0, {$endif} 0 );
{$else}
  {-ifdef GRISU1_A2F_NO_SNAN}
    // "real indefinite"
    pack_float( val_real, true, C_EXP2_SPECIAL, {$ifdef VALREAL_128} C_FLT_MANT_QNANh, {$endif} C_FLT_MANT_QNAN );
(*{-else}
    // SNaN is preferable for catching uninitialized variables access, but may cause troubles with implicit float type conversions
    pack_float( val_real, false, C_EXP2_SPECIAL, {$ifdef VALREAL_128} C_FLT_MANT_SNANh, {$endif} C_FLT_MANT_SNAN );
  {-endif}*)
{$endif}

    // search for a sign skipping leading spaces
    minus := false;
    while ( src_pos <= src_len ) do
    begin
        a := src[src_pos];
        case a of
        '+':
            begin
                inc( src_pos );
                break;
            end;
        '-':
            begin
                minus := true;
                inc( src_pos );
                break;
            end;
        else
            if a <> ' ' then
                break;
        end;
        inc( src_pos );
    end;
    if ( src_pos > src_len ) then
    begin
        // syntax: nothing to evaluate
        err_pos := src_pos;
        exit;
    end;

    // handle specials
    case src[src_pos] of
       '0' .. '9', '.', 'E', 'e': special := false;
    else
        special := true;
    end;
    if special then
    begin
        mantissa := C_FLT_MANT_INF;
{$ifdef VALREAL_128}
        mantissa_h := C_FLT_MANT_INFh;
{$endif}
        if not match_special( src_pos, src, C_STR_INF ) then
        begin
          {$ifndef GRISU1_A2F_NO_SNAN}
            if match_special( src_pos, src, C_STR_SNAN ) then
            begin
                mantissa := C_FLT_MANT_SNAN;
{$ifdef VALREAL_128}
                mantissa_h := C_FLT_MANT_SNANh;
{$endif}
            end
            else
          {$endif GRISU1_A2F_NO_SNAN}
            if match_special( src_pos, src, C_STR_QNAN ) then
            begin
              {$ifdef GRISU1_A2F_QNAN_REAL_INDEFINITE}
                minus := TRUE;
              {$endif}
                mantissa := C_FLT_MANT_QNAN;
{$ifdef VALREAL_128}
                mantissa_h := C_FLT_MANT_QNANh;
{$endif}
            end
            else
                special := false;
        end;
        if special then
        begin
            pack_float( val_real, minus, C_EXP2_SPECIAL, {$ifdef VALREAL_128} mantissa_h, {$endif} mantissa );
            src_pos := 0;
        end;
        err_pos := src_pos;
        exit;
    end;

    // consume mantissa digits skipping leading zeroes
    // empty mantissa ("0.E#", ".0E#", ".E#", "E#") is allowed at least in D5
    mantissa := 0;
{$if defined(VALREAL_80) or defined(VALREAL_128)}
    mantissa_h := 0;
{$endif VALREAL_80 | VALREAL_128}
    dig_num := 0;
    exp10 := 0;
    dig_round := 0;
    dig_sticky := 0;

    // leading zero loop
    while ( src_pos <= src_len ) and ( src[src_pos] = '0' ) do
        inc( src_pos );

    // integer part loop
    while ( src_pos <= src_len ) do
    begin
        a := src[src_pos];
        if ( a < '0' ) or ( a > '9' ) then
            break;
        b := ord(a) - ord('0');
        if ( dig_num < C_MAX_DIGITS_ACCEPTED ) then
            // normal digit
{$if defined(VALREAL_32) or defined(VALREAL_64)}
            inc( mantissa, ( mantissa shl 3 ) + mantissa + b )
{$else VALREAL_80 | VALREAL_128}
            add_digit( mantissa_h, mantissa, b )
{$endif VALREAL_*}
        else
        begin
            // over-required digits: use them for rounding later
            if ( dig_num = C_MAX_DIGITS_ACCEPTED ) then
                dig_round := b // main digit for rounding
            else
                dig_sticky := dig_sticky or b; // just "<>0" to judge "round to even" case later
            inc( exp10 ); // move [yet virtual] dot to the right
        end;
        inc( dig_num );
        inc( src_pos );
    end;

    // fraction part
    if ( src_pos <= src_len ) and ( src[src_pos] = '.' ) then
    begin
        // skip dot
        inc( src_pos );
        // leading zero loop, if integer part was 0
        if dig_num = 0 then
            while ( src_pos <= src_len ) and ( src[src_pos] = '0' ) do
            begin
                dec( exp10 ); // move the dot to the left
                inc( src_pos );
            end;
        // fraction part loop
        while ( src_pos <= src_len ) do
        begin
            a := src[src_pos];
            if ( a < '0' ) or ( a > '9' ) then
                break;
            b := ord(a) - ord('0');
            if ( dig_num < C_MAX_DIGITS_ACCEPTED ) then
            begin
                // normal digit
{$if defined(VALREAL_32) or defined(VALREAL_64)}
                inc( mantissa, ( mantissa shl 3 ) + mantissa + b );
{$else VALREAL_80 | VALREAL_128}
                add_digit( mantissa_h, mantissa, b );
{$endif VALREAL_*}
                dec( exp10 ); // move the dot to the left
            end
            else
            begin
                // over-required digits: use them for rounding later
                if ( dig_num = C_MAX_DIGITS_ACCEPTED ) then
                    dig_round := b // main digit for rounding
                else
                    dig_sticky := dig_sticky or b; // just "<>0" to judge "round to even" case later
            end;
            inc( dig_num );
            inc( src_pos );
        end;
    end;

    // round digits
{$ifndef GRISU1_A2F_HALF_ROUNDUP}
    if ( dig_round = 5 ) and ( dig_sticky = 0 ) and ( mantissa and 1 = 0 ) then
        // need to "round to even", and already even..
        dec( dig_round ); // ..so force no round-up
{$endif not GRISU1_A2F_HALF_ROUNDUP}
    if ( dig_round >= 5 ) then
    begin
        // round-up
        inc( mantissa );
{$if defined(VALREAL_80) or defined(VALREAL_128)}
        if ( mantissa = 0 ) then
            inc( mantissa_h );
{$endif VALREAL_*}
    end;

    // consume exponent digits
    exp_read := 0;
    if ( src_pos <= src_len ) then
    begin
        exp_minus := false;
        case src[src_pos] of
        'e', 'E':;
        else
            // syntax: "E" expected
            err_pos := src_pos;
            exit;
        end;
        inc( src_pos );
        if ( src_pos > src_len ) then
        begin
            // syntax: empty exponent
            err_pos := src_pos;
            exit;
        end;
        case src[src_pos] of
        '+':
            inc( src_pos );
        '-':
            begin
                exp_minus := true;
                inc( src_pos );
            end;
        end;
        while ( src_pos <= src_len ) do
        begin
            a := src[src_pos];
            if ( a < '0' ) or ( a > '9' ) then
            begin
                // syntax: bad digit
                err_pos := src_pos;
                exit;
            end;
            if ( exp_read < 100000 ) then
                inc( exp_read, ( exp_read shl 3 ) + exp_read + ord(a) - ord('0') );
         // else just syntax check
            inc( src_pos );
        end;
        if exp_minus then
            exp_read := - exp_read;
    end;
    exp_temp := exp_read + exp10;
    if ( exp_read >= 100000 ) or ( exp_temp >= C_EXP10_OVER ) then
        exp10 := C_EXP10_OVER
    else
    if ( exp_read <= - 100000 ) or ( exp_temp <= - C_EXP10_OVER ) then
        exp10 := - C_EXP10_OVER
    else
        exp10 := exp_temp;

    // nothing should remain in the "src" here
    if ( src_pos <= src_len ) then
    begin
        err_pos := src_pos;
        exit;
    end;

    // zero [or negative exponent overflow]
    if ( mantissa {$if defined(VALREAL_80) or defined(VALREAL_128)} or mantissa_h {$endif} = 0 )
    or ( exp10 <= - C_EXP10_OVER ) then
    begin
        pack_float( val_real, minus, 0, {$ifdef VALREAL_128} 0, {$endif} 0 ); // +0|-0
        err_pos := 0;
        exit;
    end;

    if ( exp10 >= C_EXP10_OVER ) then
        // exponent overflowed -> return Inf
        overflow := 1
    else
    begin
        // make DIY_FP
{$if defined(VALREAL_32) or defined(VALREAL_64)}
        n := count_leading_zero( mantissa );
        D.f := mantissa shl n;
{$else VALREAL_80 | VALREAL_128}
        if ( mantissa_h = 0 ) then
            n := count_leading_zero( mantissa ) + sizeof( mantissa_h ) * 8
        else
            n := count_leading_zero( mantissa_h );
        D.f := mantissa;
        D.fh := mantissa_h;
        diy_util_shl( D.fh, D.f, n );
{$endif VALREAL_*}
        D.e := - n;
        // get factor
        overflow := factor_10_inexact( exp10, C ); // <>0 -> over/underflow
    end;

    if ( overflow = 0 ) then
    begin
        // multiply
        if ( C.e10 <> 0 ) then
            // C <> 1
            D := diy_fp_multiply( D, C.c, TRUE );
        // calculate round increment
        if ( D.f and C_DIY_ROUND_MASK = C_DIY_ROUND_BIT ) then
            // round to even and already even
            b := 0
        else
            b := ord( D.f and C_DIY_ROUND_BIT <> 0 );
        // shift and round
{$if defined(VALREAL_32) or defined(VALREAL_64)}
        D.f := ( D.f shr C_DIY_SHR_TO_FLT ) + b;
        // handle round overflow
        if ( D.f and ( C_FLT_INT_BIT shl 1 ) <> 0 ) then
        begin
            D.f := D.f shr 1;
            inc( D.e );
        end;
{$else VALREAL_80 or VALREAL_128}
        diy_util_shr( D.fh, D.f, C_DIY_SHR_TO_FLT );
        if ( b <> 0 ) then
            diy_util_add( D.fh, D.f, 0, b );
        // handle round overflow
        if ( D.fh {$ifdef VALREAL_128} and ( C_FLT_INT_BITh shl 1 ) {$endif} <> 0 ) then
        begin
            diy_util_shr( D.fh, D.f, 1 );
            inc( D.e );
        end;
    {$if defined(grisu1_debug) and defined(VALREAL_80)}
        assert( D.fh = 0 );
    {$endif grisu1_debug}
{$endif VALREAL_*}
        // calculate exponent
        D.e := D.e + C_DIY_EXP_TO_FLT;
        if ( D.e >= C_EXP2_SPECIAL ) then
          ///////////////////
          //
          // overflow
          //
          ///////////////////
            overflow := 1
        else
        if ( D.e < - C_FRAC2_BITS ) then
          ///////////////////
          //
          // underflow
          //
          ///////////////////
            overflow := -1
        else
        if ( D.e <= 0 ) then
        begin
          ///////////////////
          //
          // denormal (and also an extreme case of "D.e=-C_FRAC2_BITS", where
          // rounding may produce either the lowest denormal or underflow)
          //
          ///////////////////
            n := 1 - D.e; // SHR amount
            // round bit
{$ifdef VALREAL_32}
            bit_round := dword(1) shl ( n - 1 );
{$endif VALREAL_32}
{$if defined(VALREAL_64) or defined(VALREAL_80)}
            bit_round := qword(1) shl ( n - 1 );
{$endif VALREAL_64 | VALREAL_80}
{$ifdef VALREAL_128}
            bit_round := 1;
            bit_round_h := 0;
            diy_util_shl( bit_round_h, bit_round, n - 1 );
            // mask for ulp and all least bits including the round one
            bit_round_mask := bit_round;
            bit_round_mask_h := bit_round_h;
            diy_util_shl( bit_round_mask_h, bit_round_mask, 2 );
            if ( bit_round_mask = 0 ) then
                dec( bit_round_mask_h );
            dec( bit_round_mask );
{$else not VALREAL_128}
            // mask for ulp and all least bits including the round one
            bit_round_mask := ( bit_round shl 2 ) - 1;
            // Note[floatx80]: works correctly despite the overflow is ignored in extreme case "D.e=-C_FRAC2_BITS"
{$endif VALREAL_*}
            // round increment
            if ( D.f and bit_round_mask = bit_round ) {$ifdef VALREAL_128} and ( D.fh and bit_round_mask_h = bit_round_h ) {$endif} then
                // round to even and already even -> no round-up
                b := 0
            else
                b := ord( ( D.f and bit_round ) {$ifdef VALREAL_128} or ( D.fh and bit_round_h ) {$endif} <> 0 );
            // shift and round
            if ( D.e = - C_FRAC2_BITS ) then
            begin
                // extreme case: rounding may result in either lowest denormal or zero
              {$ifdef VALREAL_128}
                D.fh := 0;
              {$endif VALREAL_128}
                D.f := b;
                if ( b = 0 ) then
                    overflow := -1; // underflow
            end
            else
          {$ifdef VALREAL_128}
            begin
                diy_util_shr( D.fh, D.f, n );
                if ( b <> 0 ) then
                    diy_util_add( D.fh, D.f, 0, b );
            end;
          {$else not VALREAL_128}
                D.f := ( D.f shr n ) + b;
          {$endif VALREAL_*}
            D.e := 0;
{$ifdef grisu1_debug}
          {$ifdef VALREAL_128}
            assert( ( D.f or D.fh <> 0 ) or ( overflow = -1 ) );
            assert( D.fh and not C_FLT_FRAC_MASKh = 0 );
          {$else not VALREAL_128}
            assert( ( D.f <> 0 ) or ( overflow = -1 ) );
            assert( D.f and not C_FLT_FRAC_MASK = 0 );
          {$endif VALREAL_*}
{$endif grisu1_debug}
        end
        else
        begin
          ///////////////////
          //
          // normal: 0 < D.e < C_EXP2_SPECIAL
          //
          ///////////////////
{$ifdef grisu1_debug}
          {$ifdef VALREAL_32}
            assert( D.f and not C_FLT_FRAC_MASK = C_FLT_INT_BIT );
          {$endif VALREAL_32}
          {$if defined(VALREAL_64) or defined(VALREAL_80)}
            assert( D.f and not qword(C_FLT_FRAC_MASK) = qword(C_FLT_INT_BIT) );
          {$endif VALREAL_64 | VALREAL_80}
          {$ifdef VALREAL_128}
            assert( D.fh and not C_FLT_FRAC_MASKh = C_FLT_INT_BITh );
          {$endif VALREAL_128}
{$endif grisu1_debug}
        {$ifndef VALREAL_80}
           // clear the implicit integer bit
          {$ifdef VALREAL_128}
            D.fh := D.fh and C_FLT_FRAC_MASKh;
          {$else not VALREAL_128}
            D.f := D.f and C_FLT_FRAC_MASK;
          {$endif VALREAL_*}
        {$endif not VALREAL_80}
        end;
    end;

    // final result
    if ( overflow < 0 ) then
    begin
        // underflow [+0|-0]
        pack_float( val_real, minus, 0, {$ifdef VALREAL_128} 0, {$endif} 0 );
    end
    else
    if ( overflow > 0 ) then
    begin
        // overflow [+Inf|-Inf]
        pack_float( val_real, minus, C_EXP2_SPECIAL, {$ifdef VALREAL_128} C_FLT_MANT_INFh, {$endif} C_FLT_MANT_INF );
    end
    else
    begin
        // no error
        pack_float( val_real, minus, D.e, {$ifdef VALREAL_128} D.fh, {$endif} D.f );
    end;
    err_pos := 0;

end;
